Published on

CodeFestCTF'25 - Forensics - Password Recovery

Authors

Challenge Description

Description

Solution

The provided binary passgen generates a password based on current time:

binary-running

Binary disassembly:

int __fastcall main(int argc, const char **argv, const char **envp)
{
    unsigned int v3; // eax
    int i; // [rsp+Ch] [rbp-64h]
    char v6[16]; // [rsp+10h] [rbp-60h] BYREF
    char v7[72]; // [rsp+20h] [rbp-50h] BYREF
    unsigned __int64 v8; // [rsp+68h] [rbp-8h]

    v8 = __readfsqword(0x28u);
    banner(argc, argv, envp);
    strcpy(v7, "abcdefghijklmnoqprstuvwyzxABCDEFGHIJKLMNOQPRSTUYWVZX0123456789");
    v3 = time(0LL);
    srand(v3);
    for ( i = 0; i <= 14; ++i )
        v6[i] = v7[rand() % 62];
    v6[15] = 0;
    printf("[*] HERE IS YOUR SECURELY GENERATED PASSWORD: %s\n", v6);
    return 0;
}

Here we can see that the password is generated using the srand function. The seed for the srand function is the current time.

    v3 = time(0LL);
    srand(v3);

If you dont know, srand function generates same result for same seed which means if we generate 2 password at the same time (like 20:55.13), we will get the same password.

same-password

So in thoery, 86,400 passwords can be generated in a day.

What if we bruteforce the password by generating the password for each second of Jan 26, 2019 (republic day india 2019) and try to extract the zip file using that?

This script bruteforce all passwords starting from 00:00:00 and up till 23:59:59 (date: 26th Jan 2019) and tries to extract the zip file using the generated password.

solve.py
import os
import ctypes
import zipfile
from datetime import datetime, timedelta
from pwn import log

libc = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6')

def c_srand(seed):
    libc.srand(seed)

def c_rand():
    return libc.rand()

def genPassword(seed_time):
    c_srand(int(seed_time))
    
    charset = "abcdefghijklmnoqprstuvwyzxABCDEFGHIJKLMNOQPRSTUYWVZX0123456789"
    password = ''
    
    for _ in range(15):
        index = c_rand() % len(charset)
        password += charset[index]
    
    log.info(f"Generated password: {password}")
    return password

def bruteForceZIP(zipPath):
    print(f"[INFO] Starting brute force on ZIP file '{zipPath}'")
    start_time = datetime(2019, 1, 26, 0, 0, 0)
    end_time = datetime(2019, 1, 26, 23, 59, 59)
    
    current_time = start_time
    
    with open('generated_passwords.txt', 'w') as password_file:
        while current_time <= end_time:
            password = genPassword(current_time.timestamp())
            password_file.write(password + '\n')
            try:
                with zipfile.ZipFile(zipPath, 'r') as zf:
                    zf.extractall(pwd=password.encode('utf-8'))
                
                '''Checking size of flag to see if original flag is retrieved'''
                if os.path.exists('flag.txt'):
                    flag_size = os.path.getsize('flag.txt')
                    if flag_size > 0:
                        log.success(f"Password found:- {password}")
                        log.success(f"Time used:- {current_time}")
                        log.success(f"Flag retrieved successfully ({flag_size} bytes).")
                        return password
                    else:
                        log.warn(f"Password failed: `flag.txt` is empty.")
                else:
                    log.warn(f"Password failed: `flag.txt` not created.")
            
            except (RuntimeError, zipfile.BadZipFile) as e:
                log.warn(f"Exception occurred: {e}")
            
            current_time += timedelta(seconds=1)
    
    print("[ERROR] No password found.")
    return None

if __name__ == '__main__':
    zipPath = 'encrypted.zip'
    if os.path.exists(zipPath):
        bruteForceZIP(zipPath)
    else:
        log.warn(f"'{zipPath}' help :(")

Flag:

flag