# 0xd13a

A rookie in a world of pwns

# InCTF 2020 Writeup: Coins

Coins

100

Toss a coin to your Witcher O’ Valley of Plenty O’ Valley of Plenty, oh

nc 34.74.30.191 1337

Author: III_Wolverine_III

Once we get past the PoW we are given a number of coins that a minting machine makes. Our task is to find the defective coin and to do that we can select ranges of coins whose weights will be xor’ed together and the result returned to us. We get a limited number of attempts so we have to be smart about it, and binary search should be the fastest method of narrowing it down.

xor’ing an even number of equal weights together results in 0, so that will be our indication that all the coins in that range are good. A non-zero number indicates that there is a defective coin there.

If we have 16 coins, we can start by checking coins 0 through 7. If the xor is 0, then the next range to check will be 8 through 11, and so on. A special case is when there are 2 coins in the range, i and i+1, and we cannot tell which one is the bad one. For that case we can make an additional check of i-1 and i (or i+1 and i+2).

Let’s put this algorithm in a script:

``````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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from pwn import *
import hashlib

r = remote('34.74.30.191', 1337)

# Solve the PoW challenge
challenge = r.recv().split()
hash = challenge[2]
suffix = challenge[0].split('+')[1][:-1]
print suffix, hash
s = iters.mbruteforce(lambda x: hashlib.sha256(x+suffix).hexdigest() == hash, string.letters+string.digits, 4, 'fixed')
r.send(s+"\n")

print r.recvline().strip()
print r.recvline().strip()
print r.recvline().strip()

print "###"

number = 0

# Check if all coins in the set are of equal weight
def equal_weight(a, b):
req = "{} {}".format(a, b)
print req
r.send(req+"\n")
ans = r.recvline().strip()
print ans
return ans.endswith(" 0")

def report_coin(c):
req = "! {}".format(c)
print req
r.send(req+"\n")

# Do a recursive binary search
def binary_search(low, high):
print "Searching {} {}".format(low, high)

if high < low:
return

# Only one coin is found - report it
if low == high:
report_coin(low)
return

# Check for corner cases
if low+1 == high:
if high < number:
if equal_weight(high,high+1):
report_coin(low)
return
else:
report_coin(high)
return
else:
if equal_weight(low-1,low):
report_coin(high)
return
else:
report_coin(low)
return

mid = (high + low) // 2
if (mid - low + 1) % 2 != 0:
mid += 1

# Search recursively
if not equal_weight(low,mid):
binary_search(low, mid)
else:
binary_search(mid+1,high)

# Loop over all rounds
while True:

number_line = r.recvline().strip()
print number_line
if not number_line.startswith("The number of coins in this batch"):
break
print r.recvline().strip()
number = int(number_line.split()[8])

print "###"

binary_search(0, number)

ans = r.recvline().strip()
print ans
if not ans.endswith("let's keep going!"):
break

r.interactive()
``````

Running the script gets us the flag:

``````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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
\$ python solve.py
[+] Opening connection to 34.74.30.191 on port 1337: Done
1q6wygjzbL5bWfww 9b311d4d4f7ca66ffaeab307a1bac15f2d434a1c21e1f0810570bde025dd0e46
[+] MBruteforcing: Found key: "XaHy"
Give me XXXX:
There exists a coin minting machine at Bi0S which is known for its
extermely fast minting process. However out of every batch (N coins)
it produces one coin is faulty (have a different weight compared to
the other N-1 coins). You can get information of the xor of the weight
of the coins from index i to index j (both included) by communicating
with the minting machine. Find the faulty coin (coin with different
weight) with minimum number of queries, as the minting machine has
produced by the minting machine and it is gaurenteed that in each
batch there is only one defective coin. Your query should be in the
format "i j" (without the quotes) where both i and j should lie in
the range [0, N). You can report the correct position (Of course after
solving it) in the format "! index" (without the quotes) where index
lies in the range [0, N). If you correctly identify the faulty coin
for a batch, you will continue to the next batch. If a query is given
in the wrong format or give a wrong answer you will be rejected.

###
The number of coins in this batch are 12
###
Searching 0 12
0 7
The xor of coin weights from index l to r is 23
Searching 0 7
0 3
The xor of coin weights from index l to r is 0
Searching 4 7
4 5
The xor of coin weights from index l to r is 0
Searching 6 7
7 8
The xor of coin weights from index l to r is 23
! 7
Correctly identified the faulty coin in this batch, let's keep going!
The number of coins in this batch are 473847
###
...
The xor of coin weights from index l to r is 588436
Searching 2792 2793
2793 2794
The xor of coin weights from index l to r is 588436
! 2793
Correctly identified the faulty coin in this batch, let's keep going!
Ahh the winner!! you have earned your reward, the holy piece of text that will lead you to your destination
[*] Switching to interactive mode

b'inctf{1f_y0u_c4n_dr3am_y0u_c4n_s34rch_1n_logn}'
[*] Got EOF while reading in interactive
``````

The flag is `inctf{1f_y0u_c4n_dr3am_y0u_c4n_s34rch_1n_logn}`.