So we need money >= 1000000000 to get the flag. Since we can control nonce and hex we can manipulate the plaintext during the decryption process.
To modify the money (plaintext) which is 0.0, we can use scientific notation to get big number with only 3 bytes values. To modify the money (plaintext) which is 0.0, we can use scientific notation to get big number with only 3 bytes value. Here is the detail of process
One day in April, I found these strange numbers, along with a program.
Solution
The program encrypt the image using random number generated by Java.util.Random.nextLong . nextLong is pseudorandom generator and for cracking it i used script provided by https://github.com/Cr4ckC4t/crack-java-prng/blob/main/crack-nextLong.py . Here is the full implementation for my solver
#!/usr/bin/env python3# Implement and crack java's util.Random.nextLong()# Reference: https://developer.classpath.org/doc/java/util/Random-source.htmlimport sysfrom Crypto.Util.number import*# Colorsclassfc: cyan ='\033[96m' green ='\033[92m' orange ='\033[93m' red ='\033[91m' end ='\033[0m' bold ='\033[1m'# Return the signed representation of a value# For example:# unsigned 1001 = 9# signed 1001 = -7## getSigned(9,4) == 7#defgetSigned(n,bits):if n >=2**(bits-1):return-((n^(2**bits-1))+1)else:return n# Returns the value that you need to pass to java.util.Random.setSeed()# in order to set the seed to `seed`.defreverseSeed(seed):return (seed ^0x5DEECE66D)# This function is unused in this code.# It implements the java.util.Random.setSeed() functiondefsetSeed(seed):return (seed ^0x5DEECE66D) & (2**48-1)# Implementation of java.util.Random.next(32)defnext32(seed): newseed = (seed *0x5DEECE66D+0xB) & (2**48-1)return newseed,getSigned((newseed >>16), 32)# Implementation of java.util.Random.nextLong()defnextLong(seed): seed, a =next32(seed) seed, b =next32(seed)return seed,getSigned(((a<<32)+b), 64)# Contrary to getSigned(), expects 64 bit as input# Returns the correct bit representation of the value but as positive integerdefsignedLongToInt(long): x =''if long <0: x =bin((abs(long)^(2**64-1))+1)[2:]else: x =bin(long)[2:]returnint(x,2)# Since one long token consists of two generate 32 bit tokens# and since the first token consists of the first 32 bit of the# seed for the second token - we can brute force the remaining 16# bits to find the seed that was used to create the second token.# Once we have the seed, we can create every following number.defcrackSeed(long): longbin =bin(signedLongToInt(long))[2:] # get the correct binary representation of a long longbin ='0'*(64-len(longbin))+longbin # pad seed to 64 bit lower = longbin[32:]# take lower 32 bit# Usually brute forcing the 16 LSBs of the 48 bit seed should be good enough# But in some special cases, we might need to brute force a few more bitsfor bits inrange(16,20):# amount of bits to brute forceprint(f'{fc.orange}[>]{fc.end} Brute forcing {fc.orange}{bits}{fc.end} bits...') upper = longbin[0:48-bits]# the *known* bits of the seed (start at 32)for i inrange(2**bits):# iterate over all unknown bits bini =bin(i)[2:] bini ='0'*(bits-len(bini))+bini # pad unknown bits to correct lengthprint(f'\tSeed: {fc.cyan}{upper}{fc.red}{bini}{fc.end}', end='\r') genseed = upper+bini # create the 48 bit seed ns, nb =next32(int(genseed,2))# call nextInt() with the seedif nb ==getSigned(int(lower,2),32):# compare with the known result (lower)print("\n")# The found seed is the one being used internallyprint(f"{fc.green}[>] Found the used seed: {int(genseed,2)}{fc.end}")# If you want to start a PRNG with this seed manually (e.g. Java REPL)# you'll have to use the reversed seed. seed =reverseSeed(int(genseed,2))# reverse the seedprint(f"{fc.green}[>]{fc.end} The reversed seed is: {fc.green}{seed}{fc.end}")return ns # return the next seedprint(f'\n\t{fc.red}Couldn\'t find the seed.{fc.end}')print() sys.exit(1)token =-504229198866833519png_sig =bytes.fromhex("89504E470D0A1A0A")png = png_sigpng_sig =bytes_to_long(png_sig)f =open("out.txt","r").read()exec(f"out = {f}")seed =crackSeed(png_sig^out[0])ns = seedfor i inrange(1,len(out)): ns, long =nextLong(ns) png +=long_to_bytes((out[i]^long)&0xffffffffffffffff).rjust(8,b'\x00')g =open("flag.png","wb")g.write(png)g.close()
Flag : flag{the_season_of_lies}
Order (46 solves)
Description
Python's hash function is not cryptographically secure. I thought I was cool and tried writing my own hash function that was somewhat similar. Sadly, I forgot to store my flag and all I have left is this challenge and the fact that it's output is 1. Can you help me recover the flag?
Solution
From euler's theorem we know that a^phi = 1 mod n. By using this knowledge we can get phi value from modulus then bruteforcing all possibilities through its divisor then reverse the encryption process. Here is solver i used during competition
from Crypto.Util.number import*# reverse encryption process# t = k*(phi - 1)# (flag^-1 * a) mod p = t# flag^-1 = t * a^-1# flag = ((flag ^-1)^-1)sus =11424424906182351530856980674107667758506424583604060548655709094382747184198a =19733537947376700017757804691557528800304268370434291400619888989843205833854285488738413657523737062550107458p =48743250780330593211374612602058842282787459461925115700941964201240170956193881047849685630951233309564529903a_inv =pow(a, -1, p)phi =euler_phi(p)for i indivisors(phi):ifpow(sus, i, p)==1:for t inrange(i-1, p, i): tmp = a_inv * t tmp =pow(tmp, -1, p) flag =long_to_bytes(tmp)ifb"flag{"in flag:print(flag.decode())breakbreak