[Crypto] 'bit by bit' - an example of timing attack [MapleCTF2022]

0x0 Introduction

In maple ctf 2022 (more write up at here), there is a crypto question using timing attack, which is a type of side channel attack.

timing attack basically is a side channel attack which attacker could attack a crypto system by analyzing the execution time of certain code.

Since every logical operation require time, more complex code takes more time. We can use this property to analyze codes.

For example. the second part takes longer than first part

1
2
3
4
add rip, 1

add rip, 1
add rip, 1

0x1 Server Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from hashlib import sha256
from os import urandom
import scrypt

flag = b"fakeflag"

# Convert the flag (in bytes) to bits
bits = ''.join([bin(c)[2:].zfill(8) for c in flag])
print(bits)
# Return an encrypted string based on the bit at a certain index
def encrypt(index):
assert(0 <= index < len(bits))

# Generate a random salt for the given index
salt = urandom(32)
# Depending on the bit value, hash the flag differently
if bits[index] == '0':
encrypted = scrypt.hash(flag, salt, 2 ** 16).hex()
else:
encrypted = sha256(flag + salt).hexdigest()
return encrypted[:32]


if __name__ == "__main__":
while True:
try:
print("Which bit would you like to query for?")
bit_index = int(input(">>> "))
print(encrypt(bit_index))
except Exception:
print("Something unexpected happened, please try again!")

0x2 Analysis

firstly, lets take look at how code works.

  • pad each character in the 8 bit string
  • get the bit given an index, encrypt flag using either sha256 or scrypt depending on the bit
  • return the first 32 value of encrypted flag

looks like we don't have any possible way of getting the flag.

However, since scrypt runs much slower than sha256, we can measure the time of encrypting flag. And then use time to figure out if the bit is 1 or 0.

if we measure the time, we can see scrypt is using 0.16705632209777832 seconds, while sha256 only using 0.0001506805419921875, which is 1000 times slower.

1
2
3
4
5
6
7
8
9
10
Which bit would you like to query for?
>>> 0
0 0.16705632209777832 128
69adfaef506c83899a98cc3f670a0e5d
Which bit would you like to query for?
>>> 1
1 0.0001506805419921875 64
3dc8c8cfae15c453bc162cafecc27575
Which bit would you like to query for?
>>>

0x3 exploit.py

here is my solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bits_time = []
io = connect(host, port)
index = 0
while True:
print(index)
io.sendlineafter(b">>> ", b"%d"%index)
t1 = time.time()
h = io.recvuntil(b'\n')
t2 = time.time()
bits_time.append(t2-t1)
if h.startswith(b"Something unexpected happened"):
break
index +=1


print(bits_time)
real_bits = []
for bt in bits_time:
if bt >= 0.1:
real_bits.append("0")
else:
real_bits.append("1")
print(real_bits)

for i in range(0,len(real_bits),8):
print(chr(int("".join(real_bits[i:i+8]),2)),end="")
print()

0x4 flag

maple{s!d3_ch@nn3l_a77@ck5}