Careless Padding
28 minutes to read
We are given the Python source code of the server:
#!/usr/local/bin/python
import random
import os
from secret import flag
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import json
N = 16
# 0 -> 0, 1~N -> 1, (N+1)~(2N) -> 2 ...
def count_blocks(length):
block_count = (length-1) // N + 1
return block_count
def find_repeat_tail(message):
Y = message[-1]
message_len = len(message)
for i in range(len(message)-1, -1, -1):
if message[i] != Y:
X = message[i]
message_len = i + 1
break
return message_len, X, Y
def my_padding(message):
message_len = len(message)
block_count = count_blocks(message_len)
result_len = block_count * N
if message_len % N == 0:
result_len += N
X = message[-1]
Y = message[(block_count-2)*N+(X%N)]
if X==Y:
Y = Y^1
padded = message.ljust(result_len, bytes([Y]))
return padded
def my_unpad(message):
message_len, X, Y = find_repeat_tail(message)
block_count = count_blocks(message_len)
_Y = message[(block_count-2)*N+(X%N)]
if (Y != _Y and Y != _Y^1):
raise ValueError("Incorrect Padding")
return message[:message_len]
def chal():
k = os.urandom(16)
m = json.dumps({'key':flag}).encode()
iv = os.urandom(16)
cipher = AES.new(k, AES.MODE_CBC, iv)
padded = my_padding(m)
enc = cipher.encrypt(padded)
print(f"""
*********************************************************
You are put into the careless prison and trying to escape.
Thanksfully, someone forged a key for you, but seems like it's encrypted...
Fortunately they also leave you a copied (and apparently alive) prison door.
The replica pairs with this encrypted key. Wait, how are this suppose to help?
Anyway, here's your encrypted key: {(iv+enc).hex()}
*********************************************************
""")
while True:
enc = input("Try unlock:")
enc = bytes.fromhex(enc)
iv = enc[:16]
cipher = AES.new(k, AES.MODE_CBC, iv)
try:
message = my_unpad(cipher.decrypt(enc[16:]))
if message == m:
print("Hey you unlock me! At least you know how to use the key")
else:
print("Bad key... do you even try?")
except ValueError:
print("Don't put that weirdo in me!")
except Exception:
print("What? Are you trying to unlock me with a lock pick?")
if __name__ == "__main__":
chal()
Source code analysis
The server generates a random key and a random IV, creates an AES cipher in CBC mode and encrypts the flag. We are given the IV and the ciphertext and we are allowed to decrypt arbitrary ciphertexts with a chosen IV, though not seeing the plaintext.
There is a custom padding implementation. Given that my_unpad
raises ValueError
, we have a padding oracle. If the plaintext unpads correctly, the server will show Bad key... do you even try?
, but if the padding is wrong, the server says Don't put that weirdo in me!
:
try:
message = my_unpad(cipher.decrypt(enc[16:]))
if message == m:
print("Hey you unlock me! At least you know how to use the key")
else:
print("Bad key... do you even try?")
except ValueError:
print("Don't put that weirdo in me!")
Padding implementation
Let’s have a look at the padding implementation:
def my_padding(message):
message_len = len(message)
block_count = count_blocks(message_len)
result_len = block_count * N
if message_len % N == 0:
result_len += N
X = message[-1]
Y = message[(block_count-2)*N+(X%N)]
if X==Y:
Y = Y^1
padded = message.ljust(result_len, bytes([Y]))
return padded
def my_unpad(message):
message_len, X, Y = find_repeat_tail(message)
block_count = count_blocks(message_len)
_Y = message[(block_count-2)*N+(X%N)]
if (Y != _Y and Y != _Y^1):
raise ValueError("Incorrect Padding")
return message[:message_len]
In my_padding
, the server takes the last byte of the message as X = message[-1]
and Y = message[(block_count-2)*N+(X%N)]
. Assuming that we will send at least 2 blocks, Y
is just the byte at position X % N
of the second to last block. And the message will be padded with Y
values (if X
equals Y
, then Y = Y ^ 1
).
Let’s see some examples:
>>> my_padding(b'0123456789abcdef' + b'A')
b'0123456789abcdefA111111111111111'
>>> my_padding(b'0123456789abcdef' + b'B')
b'0123456789abcdefB222222222222222'
>>> my_padding(b'0123456789abcdef' + b'C')
b'0123456789abcdefC333333333333333'
>>> my_padding(b'0123456789abcdef' + b'D')
b'0123456789abcdefD444444444444444'
>>> my_padding(b'0123456789abcdef' + b'E')
b'0123456789abcdefE555555555555555'
Since the ASCII value for A
is 0x41
, then X
is 0x41
and X % N
is just 1
, therefore, Y
is message[1]
, which is '1'
. That’s why the padding for the first string is a bunch of '1'
s.
Notice that if we use B
instead of A
, now the padding is message[2]
. Then, using C
, the padding is message[3]
and so on.
Since N = 16
, there is a loop:
>>> my_padding(b'0123456789abcdef' + b'!')
b'0123456789abcdef!111111111111111'
>>> my_padding(b'0123456789abcdef' + b'1')
b'0123456789abcdef1000000000000000'
>>> my_padding(b'0123456789abcdef' + b'A')
b'0123456789abcdefA111111111111111'
>>> my_padding(b'0123456789abcdef' + b'Q')
b'0123456789abcdefQ111111111111111'
>>> my_padding(b'0123456789abcdef' + b'a')
b'0123456789abcdefa111111111111111'
>>> my_padding(b'0123456789abcdef' + b'q')
b'0123456789abcdefq111111111111111'
As can be seen, every ASCII letter that gives a remainder of 1
when dividing by N
will result in a padding of message[1]
. That is, 0x21
(!
), 0x31
(1
), 0x41
(A
), 0x51
(Q
), 0x61
(a
) and 0x71
(q
). There is an exception with 0x31
(1
), where the padding is 0
(0x30 = 0x31 ^ 1
) because the last byte of the message is also a 1
.
Padding Oracle Attack
The idea of a Padding Oracle Attack is to modify the last bytes of the second to last ciphertext block, so that they affect the last bytes of the last block. Since the padding is at the end of the last block, we will know if the message unpads correctly or not and guess plaintext bytes depending on that:
The typical padding implementation is PKCS7, which is predictable and allows to recover the plaintext (for more information, read I know Mag1k). This time, the padding implementation won’t give us the plaintext directly, but a set of possible values of plaintext bytes.
Solution
My solution is based on the Padding Oracle Attack and a bit of guessing to recover the plaintext message (the flag).
First of all, what is being encrypted is a JSON object that contains the flag. The message with the test flag is:
{"key": "hitcon{tmperory_flag_for_testing_and_no_more}"}
Let’s divide it in blocks:
>>> import json
>>> flag = "hitcon{tmperory_flag_for_testing_and_no_more}"
>>> m = json.dumps({'key': flag}).encode()
>>> padded = my_padding(m)
>>> print('\n'.join(padded[i:i+N].decode() for i in range(0, len(padded), N)))
{"key": "hitcon{
tmperory_flag_fo
r_testing_and_no
_more}"}________
Notice that we already know the first plaintext block ({"key": "hitcon{
). We can use it to guess the padding byte of the last block (we will always send two blocks).
If we send IV, ct[:16]
and ct[16:32]
to the server, the plaintext will be exactly {"key": "hitcon{
and tmperory_flag_fo
. The server will try to unpad the result. In this case, o
is the padding byte, and X
is 'f'
(0x66
). Therefore, the expected padding is message[6]
, which is ':'
, and it is not correct:
>>> import os
>>> from Crypto.Cipher import AES
>>>
>>> k = os.urandom(16)
>>> iv = os.urandom(16)
>>>
>>> ct = AES.new(k, AES.MODE_CBC, iv).encrypt(padded)
>>> ct
b'\x0f\xae\xa0\x18\xceh\x06\x08\xd42\xc6=\xf8\xce\x1c\xc4\xcbUn\xfd4\xd7+\x95\xc1\x94\xa5o\xaf\x01\xcb\x0c\xea\x05\xee\xb1\xbf\xb0\xd7\xdb\xa2Cw\x12A\x94\xad\xb6\xe2\x16A$\xe4\xb4r\x1d\x0c\xeadW\xe4\n\xb1\x8f'
>>>
>>> AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:16] + ct[16:32])
b'{"key": "hitcon{tmperory_flag_fo'
>>> my_unpad(AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:16] + ct[16:32]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in my_unpad
ValueError: Incorrect Padding
Instead of modifying the second to last ciphertext block, I will modify the IV. My idea is to put an o
character in message[6]
. At this point, the server will unpad correctly.
Cheating a bit, I know that it is index 6
, I want an o
and I have :
.
>>> iv[6] ^ ord('o') ^ ord(':')
112
>>> AES.new(k, AES.MODE_CBC, iv[:6] + bytes([112]) + iv[7:]).decrypt(ct[:16] + ct[16:32])
b'{"key"o "hitcon{tmperory_flag_fo'
>>> my_unpad(AES.new(k, AES.MODE_CBC, iv[:6] + bytes([112]) + iv[7:]).decrypt(ct[:16] + ct[16:32]))
b'{"key"o "hitcon{tmperory_flag_f'
Let’s now assume that we don’t know that we need an o
, we can iterate until we don’t get a padding error:
>>> for b in range(256):
... try:
... my_unpad(AES.new(k, AES.MODE_CBC, iv[:6] + bytes([iv[6] ^ b ^ ord(':')]) + iv[7:]).decrypt(ct[:16] + ct[16:32]))
... print(b, chr(b))
... except ValueError:
... pass
...
b'{"key"n "hitcon{tmperory_flag_f'
110 n
b'{"key"o "hitcon{tmperory_flag_f'
111 o
We get the o
(n
is also valid due to XOR with 1).
And we can iterate through all the indices of the IV to determine where is the padding byte in the first plaintext block:
>>> pt = b'{"key": "hitcon{'
>>>
>>> for i in range(16):
... for b in range(256):
... try:
... my_unpad(AES.new(k, AES.MODE_CBC, iv[:i] + bytes([iv[i] ^ b ^ pt[i]]) + iv[i + 1:]).decrypt(ct[:16] + ct[16:32]))
... print(i, b, chr(b))
... except ValueError:
... pass
...
b'{"key"n "hitcon{tmperory_flag_f'
6 110 n
b'{"key"o "hitcon{tmperory_flag_f'
6 111 o
At this point, we know exactly that the last byte of the last plaintext block is an n
or an o
.
Furthermore, we know that the ASCII value of the second to last byte gives a remainder of 6
when divided by N
(so, it must be one of 0x26
, 0x36
, 0x46
, 0x56
, 0x66
, 0x76
). Indeed, it is f
(0x66
).
For the next byte (the third to last), we need to guess the last (n
or o
) and the second to last (&
, 6
, F
, V
, f
or v
). We will take one combination (let’s say n
, &
) and attempt to fix the first ciphertext block so that it modifies the second ciphertext block to end with nn
and then with &&
:
>>> pt = b'{"key": "hitcon{..............&n'
>>>
>>> padding = pt[-1]
>>> AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:14] + bytes([ct[14] ^ pt[16:32][14] ^ padding]) + ct[15:16] + ct[16:32])
b'\x1b\xd1\x06;\x16{\xfe\x03NIN\xe5\xd5\xcd\x1d\x11tmperory_flag_.o'
>>>
>>> padding = pt[-2]
>>> AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:15] + bytes([ct[15] ^ pt[16:32][15] ^ padding]) + ct[16:32])
b'\xf9_\xc9\x00wG\xf0C\x8aX\xa4h\xba"\x1f\xbatmperory_flag_f\''
Oh, it doesn’t look as expected. But this is correct, since &n
is an incorrect guess. Instead, if we use fo
as a guess, we have the expected result:
>>> pt = b'{"key": "hitcon{..............fo'
>>>
>>> padding = pt[-1]
>>> AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:14] + bytes([ct[14] ^ pt[16:32][14] ^ padding]) + ct[15:16] + ct[16:32])
b'\x98u\xbc\xc8\xba^\xfa\xe14\xd5%\x86\x1a\xa2o\xf1tmperory_flag_oo'
>>>
>>> padding = pt[-2]
>>> AES.new(k, AES.MODE_CBC, iv).decrypt(ct[:15] + bytes([ct[15] ^ pt[16:32][15] ^ padding]) + ct[16:32])
b'\xd8k\x041$Y\x84\xa66\xb2\x0eu\xdf[\x91\xa5tmperory_flag_ff'
But remember we only have a padding oracle, so we will iterate again over the IV to see the padding results.
Taking again the previous examples, we see these results for &n
:
>>> pt = b'{"key": "hitcon{..............&n'
>>>
>>> for padding in (pt[-1], pt[-2]):
... print('Force padding to', chr(padding) * 2)
... for i in range(16):
... for b in range(256):
... try:
... _iv = iv[:i] + bytes([b]) + iv[i + 1:]
... _ct = ct[:14] + bytes([ct[14] ^ pt[16:32][14] ^ padding, ct[15] ^ pt[16:32][15] ^ padding]) + ct[16:32]
... my_unpad(AES.new(k, AES.MODE_CBC, _iv).decrypt(_ct))
... print([chr(x) for x in range(0x20, 0x7f) if x % N == i])
... except ValueError:
... pass
...
Force padding to nn
b'\x1b\xd1\x06;\x16{\xfe\x03NIN\xe5\xd5\xcdn\x11tmperory_flag_.'
['.', '>', 'N', '^', 'n', '~']
b'\x1b\xd1\x06;\x16{\xfe\x03NIN\xe5\xd5\xcdo\x11tmperory_flag_.'
['.', '>', 'N', '^', 'n', '~']
Force padding to &&
b'\xf9_\xc9\x00wG\'C\x8aX\xa4h\xba"\x1f\xbatmperory_flag_f'
['&', '6', 'F', 'V', 'f', 'v']
b'\xf9_\xc9\x00wG&C\x8aX\xa4h\xba"\x1f\xbatmperory_flag_f'
['&', '6', 'F', 'V', 'f', 'v']
And these results for fo
:
>>> pt = b'{"key": "hitcon{..............fo'
>>>
>>> for padding in (pt[-1], pt[-2]):
... print('Force padding to', chr(padding) * 2)
... for i in range(16):
... for b in range(256):
... try:
... _iv = iv[:i] + bytes([b]) + iv[i + 1:]
... _ct = ct[:14] + bytes([ct[14] ^ pt[16:32][14] ^ padding, ct[15] ^ pt[16:32][15] ^ padding]) + ct[16:32]
... my_unpad(AES.new(k, AES.MODE_CBC, _iv).decrypt(_ct))
... print([chr(x) for x in range(0x20, 0x7f) if x % N == i])
... except ValueError:
... pass
...
Force padding to oo
b'\x98u\xbc\xc8\xba^\xfa\xe14\xd5%\x86\x1a\xa2ontmperory_flag_'
['/', '?', 'O', '_', 'o']
b'\x98u\xbc\xc8\xba^\xfa\xe14\xd5%\x86\x1a\xa2ootmperory_flag_'
['/', '?', 'O', '_', 'o']
Force padding to ff
b'\xd8k\x041$Y\x84\xa66\xb2\x0eu\xdf[\x91ftmperory_flag_'
['/', '?', 'O', '_', 'o']
b'\xd8k\x041$Y\x84\xa66\xb2\x0eu\xdf[\x91gtmperory_flag_'
['/', '?', 'O', '_', 'o']
Do you see the difference? If we guess correctly, the set of expected characters to be on the third to last byte will be the same (this time we expect an _
, so ['/', '?', 'O', '_', 'o']
looks good).
Let’s continue with the example testing for ?fo
and then _fo
:
>>> pt = b'{"key": "hitcon{.............?fo'
>>>
>>> for padding in (pt[-1], pt[-2], pt[-3]):
... print('Force padding to', chr(padding) * 3)
... for i in range(16):
... for b in range(256):
... try:
... _iv = iv[:i] + bytes([b]) + iv[i + 1:]
... _ct = ct[:13] + bytes([ct[j] ^ pt[16:32][j] ^ padding for j in range(16 - 3, 16)]) + ct[16:32]
... my_unpad(AES.new(k, AES.MODE_CBC, _iv).decrypt(_ct))
... print([chr(x) for x in range(0x20, 0x7f) if x % N == i])
... except ValueError:
... pass
...
Force padding to ooo
b'\x19\x91\x94\xd0P\xdf\xeb\x110\xd0\xb8 \t\tSntmperory_flag\x0f'
['/', '?', 'O', '_', 'o']
b'\x19\x91\x94\xd0P\xdf\xeb\x110\xd0\xb8 \t\tSotmperory_flag\x0f'
['/', '?', 'O', '_', 'o']
Force padding to fff
b'\x16\xae\xf0\xddMzf\x7f\xb2SAx\xf5^;\xa6tmperory_flag\x06'
['&', '6', 'F', 'V', 'f', 'v']
b'\x16\xae\xf0\xddMzg\x7f\xb2SAx\xf5^;\xa6tmperory_flag\x06'
['&', '6', 'F', 'V', 'f', 'v']
Force padding to ???
b'@\xcb\xa1\x16\x8f\xe2FYt\x1cy\x1b\xed\xf2o?tmperory_flag_'
['/', '?', 'O', '_', 'o']
b'@\xcb\xa1\x16\x8f\xe2FYt\x1cy\x1b\xed\xf2o>tmperory_flag_'
['/', '?', 'O', '_', 'o']
>>>
>>> pt = b'{"key": "hitcon{............._fo'
>>>
>>> for padding in (pt[-1], pt[-2], pt[-3]):
... print('Force padding to', chr(padding) * 3)
... for i in range(16):
... for b in range(256):
... try:
... _iv = iv[:i] + bytes([b]) + iv[i + 1:]
... _ct = ct[:13] + bytes([ct[j] ^ pt[16:32][j] ^ padding for j in range(16 - 3, 16)]) + ct[16:32]
... my_unpad(AES.new(k, AES.MODE_CBC, _iv).decrypt(_ct))
... print([chr(x) for x in range(0x20, 0x7f) if x % N == i])
... except ValueError:
... pass
...
Force padding to ooo
b'A\x95Y\x9d\xbcf\x1co\x90\x94\xba{\xfb\x0bn\x1etmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
b'A\x95Y\x9d\xbcf\x1cn\x90\x94\xba{\xfb\x0bn\x1etmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
Force padding to fff
b'\xc8\xd67\xcf\xbe~\x9afAf\xf7\xe0\x9eZ,\x7ftmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
b'\xc8\xd67\xcf\xbe~\x9agAf\xf7\xe0\x9eZ,\x7ftmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
Force padding to ___
b'\x9aZK\xee1\xc4\xc8^\x99\x15\xc0a\xc2/Vctmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
b'\x9aZK\xee1\xc4\xc8_\x99\x15\xc0a\xc2/Vctmperory_flag'
["'", '7', 'G', 'W', 'g', 'w']
Again, if we guess correctly, we expect all the sets of possible characters to be the same.
Using this, we can guess the characters of the flag one by one with trial and error and a bit of intuition.
Implementation
We will be doing a lot of queries to the server, and the server disconnects after 20 seconds. Therefore, we will use the trick from Share to send several payloads in the same request and receive the results in the same response, and also renew the connection when needed (notice that Padding Oracle Attack is agnostic to the AES key).
I will use these helper functions:
def get_process():
if len(sys.argv) < 3:
return process(['python3', 'chal.py'], level='CRITICAL')
host, port = sys.argv[1], sys.argv[2]
return remote(host, port, level='CRITICAL')
def prepare():
io = get_process()
io.recvuntil(b"Anyway, here's your encrypted key: ")
enc_data = bytes.fromhex(io.recvline().decode())
io.recv()
iv, ct = enc_data[:16], enc_data[16:]
if len(sys.argv) < 3:
pt = sys.argv[1].encode()
else:
pt = sys.argv[3].encode()
assert len(pt) % 16 == 0
if len(pt) > 32:
iv = ct[(len(pt) // 16 - 3) * 16: (len(pt) // 16 - 2) * 16]
ct = ct[(len(pt) // 16 - 2) * 16:]
pt = pt[(len(pt) // 16 - 2) * 16:]
known = pt[::-1].index(b'.')
I will use a command-line argument to specify the known plaintext, the script will show the set of possible characters of the next unknown plaintext character.
In function prepare
I create the connection and format the IV, the ciphertext and the plaintext to behave as IV plus two ciphertext blocks (if we already know two plaintext blocks, then the IV will be the first ciphertext block).
As in the examples above, I use dots (.
) to indicate unknown plaintext characters.
Next, we perform the Padding Oracle Attack for the last byte of the block:
io, iv, ct, pt, known = prepare()
if known == 0:
for i in range(16):
payload = ''
for b in range(256):
_iv = iv[:i] + bytes([b]) + iv[i + 1:]
_ct = ct[:16] + ct[16:32]
payload += _iv.hex() + _ct.hex() + '\n'
io.send(payload.encode())
for b in range(256):
if b'weirdo' not in io.recvline():
print(chr(iv[i] ^ b ^ pt[i]))
io.recv()
exit()
Notice how I send 256 queries within a single message and find the ones that do not contain the word weirdo
(these ones have not raised ValueError
). The result for this procedure is a set of two consecutive bytes.
If there are some plaintext bytes known, then we do the other algorithm to get a set of 5 possible characters. If all the sets are equal, we can pick one and try the next index:
for k in range(1, known + 1):
padding = pt[-k]
print('Force padding to', chr(padding) * known)
for i in range(16):
payload = ''
for b in range(256):
_iv = iv[:i] + bytes([b]) + iv[i + 1:]
_ct = ct[:16 - known] + \
bytes([ct[j] ^ pt[16:32][j] ^ padding for j in range(16 - known, 16)]) + \
ct[16:32]
payload += _iv.hex() + _ct.hex() + '\n'
io.send(payload.encode())
for b in range(256):
if b'weirdo' not in io.recvline():
print([chr(x) for x in range(0x20, 0x7f) if x % N == i])
io.recv()
io.close()
io, iv, ct, pt, _ = prepare()
Flag
Let’s start querying:
$ H=chal-careless-padding.chal.hitconctf.com
$ P=11111
$ python3 solve.py $H $P '{"key": "hitcon{................'
w
v
Let’s try both possible characters:
$ python3 solve.py $H $P '{"key": "hitcon{...............w'
Force padding to w
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
$ python3 solve.py $H $P '{"key": "hitcon{...............v'
Force padding to v
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Alright, let’s try 4
, d
and t
with both w
and v
:
$ python3 solve.py $H $P '{"key": "hitcon{..............4w'
Force padding to ww
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to 44
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
$ python3 solve.py $H $P '{"key": "hitcon{..............4v'
Force padding to vv
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 44
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{..............dw'
Force padding to ww
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to dd
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
$ python3 solve.py $H $P '{"key": "hitcon{..............dv'
Force padding to vv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to dd
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
$ python3 solve.py $H $P '{"key": "hitcon{..............tw'
Force padding to ww
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to tt
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
$ python3 solve.py $H $P '{"key": "hitcon{..............tv'
Force padding to vv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to tt
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
It seems clear that 4v
is correct, so let’s continue with ['#', '3', 'C', 'S', 'c', 's']
:
$ python3 solve.py $H $P '{"key": "hitcon{.............#4v'
Force padding to vvv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to 444
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Force padding to ###
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{.............34v'
Force padding to vvv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to 444
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Force padding to 333
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{.............C4v'
Force padding to vvv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to 444
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Force padding to CCC
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{.............S4v'
Force padding to vvv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to 444
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Force padding to SSS
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{.............c4v'
Force padding to vvv
['&', '6', 'F', 'V', 'f', 'v']
['&', '6', 'F', 'V', 'f', 'v']
Force padding to 444
['$', '4', 'D', 'T', 'd', 't']
['$', '4', 'D', 'T', 'd', 't']
Force padding to ccc
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
$ python3 solve.py $H $P '{"key": "hitcon{.............s4v'
Force padding to vvv
['/', '?', 'O', '_', 'o']
['/', '?', 'O', '_', 'o']
Force padding to 444
['/', '?', 'O', '_', 'o']
['/', '?', 'O', '_', 'o']
Force padding to sss
['/', '?', 'O', '_', 'o']
['/', '?', 'O', '_', 'o']
Alright, so s4v
, the next possible characters are ['/', '?', 'O', '_', 'o']
. Let’s try directly with _
:
$ python3 solve.py $H $P '{"key": "hitcon{............_s4v'
Force padding to vvvv
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to 4444
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to ssss
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to ____
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Great! If we continue this process by guessing and trying characters, we will end up with:
$ python3 solve.py $H $P '{"key": "hitcon{.4dd1ng_w0n7_s4v'
Force padding to vvvvvvvvvvvvvvv
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to 444444444444444
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to sssssssssssssss
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to _______________
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to 777777777777777
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to nnnnnnnnnnnnnnn
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to 000000000000000
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to wwwwwwwwwwwwwww
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to _______________
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to ggggggggggggggg
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to nnnnnnnnnnnnnnn
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to 111111111111111
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to ddddddddddddddd
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to ddddddddddddddd
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to 444444444444444
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
From the context of the flag message, we can be sure that the next byte is either P
or p
, but we cannot confirm which one of those is the good one.
So, let’s think it is p
and move on to the next block:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v................'
a
`
It is pretty clear that the correct character is a
due to flag formats:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v...............a'
Force padding to a
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
The next one is r
:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v..............ra'
Force padding to aa
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
Force padding to rr
[' ', '0', '@', 'P', '`', 'p']
[' ', '0', '@', 'P', '`', 'p']
And so on until we get:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v._y0u_Fr0m_4_0ra'
Force padding to aaaaaaaaaaaaaaa
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to rrrrrrrrrrrrrrr
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 000000000000000
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to _______________
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 444444444444444
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to _______________
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to mmmmmmmmmmmmmmm
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 000000000000000
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to rrrrrrrrrrrrrrr
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to FFFFFFFFFFFFFFF
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to _______________
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to uuuuuuuuuuuuuuu
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 000000000000000
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to yyyyyyyyyyyyyyy
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to _______________
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
So there is no doubt. It is a 3
.
Now, again we do the same process:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0ra................'
7
6
And eventually we get:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0ra.13_617aa68c06d7'
Force padding to 777777777777777
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to ddddddddddddddd
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 666666666666666
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 000000000000000
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to ccccccccccccccc
...
Force padding to 888888888888888
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 666666666666666
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to aaaaaaaaaaaaaaa
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to aaaaaaaaaaaaaaa
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 777777777777777
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 111111111111111
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 666666666666666
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to _______________
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 333333333333333
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
Force padding to 111111111111111
['#', '3', 'C', 'S', 'c', 's']
['#', '3', 'C', 'S', 'c', 's']
So again, we know that we can have a C
or a c
. For the moment, let’s assume it’s a c
.
Notice that there are some hexadecimal characters at the end of the flag, which are not predictable given the context…
This part was a bit more difficult:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7................'
8
9
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7...............8'
Force padding to 8
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7...............9'
Force padding to 9
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Since we are dealing with hexadecimal digits, let’s try only 5
and e
:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7..............58'
Force padding to 88
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
Force padding to 55
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7..............59'
Force padding to 99
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
Force padding to 55
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7..............e8'
Force padding to 88
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to ee
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7..............e9'
Force padding to 99
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
Force padding to ee
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
None of the above looks good… This happens because the next character is also one of the tested characters… After a lot of tests, I found out that e8e8
works:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7............e8e8'
Force padding to 8888
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
Force padding to eeee
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
Force padding to 8888
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
Force padding to eeee
[')', '9', 'I', 'Y', 'i', 'y']
[')', '9', 'I', 'Y', 'i', 'y']
From here, we can continue smoothly until:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7.b91f57d1969e8e8'
Force padding to 888888888888888
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to eeeeeeeeeeeeeee
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 888888888888888
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to eeeeeeeeeeeeeee
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 999999999999999
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 666666666666666
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 999999999999999
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 111111111111111
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to ddddddddddddddd
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 777777777777777
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 555555555555555
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to fffffffffffffff
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 111111111111111
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to 999999999999999
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
Force padding to bbbbbbbbbbbbbbb
['!', '1', 'A', 'Q', 'a', 'q']
['!', '1', 'A', 'Q', 'a', 'q']
So, we can be sure that it is 1
or a
.
For the last block, we can take into account that the plaintext ends with }"}
. Therefore, the padding will take X
as 0x7d
(the ASCII value of }
) and then X % N
is 13
, so the padding byte will be message[13]
from the second to last block, which we already know it is 8
. As a result, we can increase padding until we see a normal set of values:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d71b91f57d1969e8e8............}"}8'
Force padding to 8888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to }}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
Force padding to """"
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to }}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d71b91f57d1969e8e8...........}"}88'
Force padding to 88888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to 88888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to }}}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
Force padding to """""
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to }}}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d71b91f57d1969e8e8..........}"}888'
Force padding to 888888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to 888888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to 888888
['-', '=', 'M', ']', 'm', '}']
['-', '=', 'M', ']', 'm', '}']
Force padding to }}}}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
Force padding to """"""
["'", '7', 'G', 'W', 'g', 'w']
["'", '7', 'G', 'W', 'g', 'w']
Force padding to }}}}}}
['(', '8', 'H', 'X', 'h', 'x']
['(', '8', 'H', 'X', 'h', 'x']
Until we have:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d71b91f57d1969e8e8...}"}8888888888'
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to 8888888888888
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to }}}}}}}}}}}}}
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to """""""""""""
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
Force padding to }}}}}}}}}}}}}
['"', '2', 'B', 'R', 'b', 'r']
['"', '2', 'B', 'R', 'b', 'r']
So, we have either a 2
or a b
. Following the same process, we will have:
$ python3 solve.py $H $P '{"key": "hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d71b91f57d1969e8e8.32}"}8888888888'
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 888888888888888
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to }}}}}}}}}}}}}}}
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to """""""""""""""
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to }}}}}}}}}}}}}}}
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 222222222222222
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
Force padding to 333333333333333
['%', '5', 'E', 'U', 'e', 'u']
['%', '5', 'E', 'U', 'e', 'u']
So, the next (and last) character must be a 5
or an e
.
After testing all the possibilities (P
or p
/ C
or c
/ 1
or a
/ 5
or e
) in the CTF dashboard, we end up finding the flag:
hitcon{p4dd1ng_w0n7_s4v3_y0u_Fr0m_4_0rac13_617aa68c06d7ab91f57d1969e8e8532}
The full script can be found in here: solve.py
.