# P.W.N. CTF 2018 ## City RSA - Points:557 ### Challenge ``` I know that frank that a**hole ate the last cookie, but no one believes me. He wrote himself a service to sign messages, I found part of the source. It runs here: nc rsa.uni.hctf.fun 4422 Can you get me the message "YES, I did eat the last cookie" signed by his service? I added a verifier for you using his public key: nc checker.uni.hctf.fun 31337 ``` ### Analysis The sender message is signed by RSA in main.c ``` printf("Enter message:"); ..... /* Sign via RSA */ city_rsa_init(&cfg, p_str, q_str, p_inv_str, d_str, e_str, d_p_str, d_q_str); //city_print_config(&cfg); city_rsa_sign(&cfg, result, input_hex); printf("The signature of your message is: 0x%s\n", result); ``` The receiver verify message by main.py ``` #!/usr/bin/env python3 pubkey = { 'e': 0x10001, 'N': 0x98ac865ef6a31313e50fb37853ce96804cb2d864e2a4d14bf7cca85a444a40b453de7c3ae8416e8976cd1cac7f548a43fe8c2eb3d4cfcd3808cf9458c0c87bf4c037d515d22d1299b72e79fcd4a1d1531789cb3013031fb0e28fdfe73f090027b3b3428cacef6dbf7823d5da8d3158101e0c07e707224d451fcbb3114ab85a925bcb7faf9b317bbbddba81285ab93f0ee5f968b258f4675e9d893ec7f0e8379b67527d78fe920ab201cb3a6459d4f3902754b36e3264db7727c6d32e014593c39991f54c7b034d69b986616a39454c85d9e032afa853a6e12fea06472ed3573707da3df9ca7ce8d2c3b820e745da6e3cc523789f858d98645ea042bb54b463d3 } def main(): ct = int(input("Input signed message:"), 16) msg = pow(ct, pubkey["e"], pubkey["N"]) msg = int.to_bytes(msg, 128, "big") if b"YES, I did eat the last cookie" in msg: flag = open("/opt/flag.txt").read() print(flag) else: print("Nope, sorry.") if __name__ == '__main__': main() ``` If signed message contains 'YES, I did eat the last cookie', we can have flag. However, the service block us from getting the message signed directly by checking if 'YES' is inside the message. ### RSA Blind Attack? Apparently, we can use RSA blind attack to forge the signed message. However, the input length and characters are restricted by c api. Only 30 characters can be input and the maximum value of character have to be under 0x7f. The blind attack attack can bypass the check by... $c \equiv m^d\ (\ mod\ N\ )$ $c' \equiv m^dr^d\ (\ mod\ N\ )$ $c \equiv c'r^{-1}\ (\ mod\ N\ )$ But, we cannot send $m^dr^d$ to service and get signed. ### Exploit Since we cannot use RSA blind attack directly, we can try to factor the forged message and use modular multiplicative inverse to find all our ingredients. #### find possible prime factors use sage to find prime factors. ``` msg = 'YES, I did eat the last cookie' import binascii for a in string.ascii_letters: tmp = msg+a v = int(binascii.hexlify(tmp),16) factor(v) ``` after few minues, we have a good candidate. ``` 2 * 3 * 5 * 313 * 379 * 33311 * 15239591 * 2184081073 * 454801932889 * 4706963699932381 * 18672853823680873391 ``` then, we try to send those factors into sign service with different multiplier to control our characters are under 0x7f. ``` (1, 2) (1, 3) (1, 5) (1, 313) (3, 379) (2, 33311) (8, 15239591) (12, 2184081073) (73, 454801932889) (21, 4706963699932381) (31, 18672853823680873391L) ``` Here is the final exploit. ``` from pwn import * import binascii from Crypto.Util.number import * import gmpy msg = 'YES, I did eat the last cookie' N = 0x98ac865ef6a31313e50fb37853ce96804cb2d864e2a4d14bf7cca85a444a40b453de7c3ae8416e8976cd1cac7f548a43fe8c2eb3d4cfcd3808cf9458c0c87bf4c037d515d22d1299b72e79fcd4a1d1531789cb3013031fb0e28fdfe73f090027b3b3428cacef6dbf7823d5da8d3158101e0c07e707224d451fcbb3114ab85a925bcb7faf9b317bbbddba81285ab93f0ee5f968b258f4675e9d893ec7f0e8379b67527d78fe920ab201cb3a6459d4f3902754b36e3264db7727c6d32e014593c39991f54c7b034d69b986616a39454c85d9e032afa853a6e12fea06472ed3573707da3df9ca7ce8d2c3b820e745da6e3cc523789f858d98645ea042bb54b463d3 def getsign(m): context.log_level = 'WARN' p = remote('rsa.uni.hctf.fun', 4422) dmsg = long_to_bytes(m) p.sendline(dmsg) p.recvuntil('The signature of your message is: ') m = int(p.recvline(), 16) p.close() return m x100 = gmpy.invert(getsign(0x100), N) d = [2, 3, 5, 313, 379, 33311, 15239591, 2184081073, 454801932889, 4706963699932381, 18672853823680873391] d = [2, 3, 5, 313, 379, 33311, 15239591, 2184081073, 454801932889, 4706963699932381, 18672853823680873391] mods = {} flag = 1 for v in d: for i in range(1, 0xff): m = long_to_bytes(v*i) found = True for n in m: if ord(n) > 0x79: found = False break if found: print(i, v) if i == 1: mods[v] = (getsign(v<<8) * int(x100))%N flag *= mods[v] else: tmp = (getsign(v*i<<8) * int(x100))%N if i in mods: flag *= (tmp*gmpy.invert(mods[i], N)) % N else: iinvert = (getsign(i<<8) * int(x100))%N flag *= (tmp*gmpy.invert(iinvert, N)) % N break print(hex(flag%N)) print(pow(flag%N, 0x10001, N)) ``` Send the signed message to receiver to get the flag. ``` % nc checker.uni.hctf.fun 31337 Input signed message:0x85a8277792bcd656724e92dad50ee5ed31de54ecf746849a0605be3afd27c75492196d5cbc35957f85d0f962aa520aab50b83025797f1f7c3f5ae84b1e66976a520d5d98c4b88eb19078ec78c7d07ad5149e382ffba52d4e9d2ad40718545c3a1296b3026324d0e6a12141363ef272e42139438cd180a94b350818cd39c24f2c6d0241d80e01febf305f0866111816ff1be8823a66eed866545d1dab69ba0748d17b5344a75ea3d340bc6b2e0ca6db3c3db2bc7e22a0ce3b8ea06851ce2be7a16ff3931d5e8cb8b01b757aa6c975e98a7fb36acd1eb8b96210affeaa638b93fa9fc68960544a3b7df88d1a496b107125465e479e99d87b47a825aed9b54095d4 flag{https://www.youtube.com/watch?v=MpMBETNC-44#C0ngr4tz} ``` It is an excellent challenge, good job.