Understanding the question:
There are 2 files on the server – riddle.py and secret.py – if you solve riddle.py the secret.py consisting the flag is printed. The riddle itself is somewhat of an alteration to the classic “liars riddle” – given 3 people – one that always tells the truth, one that always tells a lie and one that can tell a lie and can tell the truth, detect who is who by asking anyone of them 3 questions.
Variable of this riddle:
t1: a tuple of lambdas – one that always returns the same output as input – AKA the truth teller (on true expression it returns that it is true and on False expression it returns that is false), one that returns not on the expression (AKA the liar) and one that returns random choice between true and false. (do_round)
t2: a tuple of the truth teller and the liar. (do_round)
m1: the order of the liar, truth teller and random teller (this is what we look for) – in the Q&A format, A is the first index of m1, B is the second and C is the third. (calc)
m2: called X in our Q&A (calc) – this indicates whether or not our expression will remain the same or be the opposite. (calc returns t2[X] on the manipulated expression)
i: (calc) the function we choose to use (AKA the person we choose to ask)
r: (calc) the expression we input (AKA the question we ask).
We have a question syntax that allows us to choose which function we use in t1 and check whether A,B,C (what we look for), X, ==, 0, 1, 2 are equal, and using ‘(‘ and ‘)’ we can ask multiple equals on the same question (for example we can ask 0 X==2 or 0 (X==2)==(A==1))
The answer of the question: t2[X](t1[m1[i]](r)) – m1[i] – the index of the function we use on t1 – (m1 is randomized) running it on our question (r) and running t2[X] on the expression we received from (t1[m1[i]](r)) – which can return the expression as is or return the opposite.
We need to get A,B and C after 3 questions. Our goal after the first question, is to get an index in m1 that is not 2 (the random function). In the first question we have 0 information, so we use index 0 in our question (we use the A (m1) function of t1), and want to receive an answer that gives us which of the m1[indexes] (B or C) is not 2 (random) for sure. If A is 2 –the answer doesn’t matter – anyway we will get a non-random function. If m1=A is 1, if X=1 we will return the expression (false on false = true) and if X=0 we will return the opposite of the expression (true(false) = false) – so expanding this logic to A=0 will bring us (X==A) == (B==2) will return whether C is definitely not 2 (random) or B is definitely not 2. (false)
After the first question we know an index that is not 2. Let’s assume it is C (if it was B, then replace the index of m1 to be 1 and replace every C reference with B – it is symmetric). So now we want to understand whether C is 0 or 1 (truth teller or false teller) – 2 (use m1[C]) (X == C) is telling us whether the expression returned is “as is” if true or the opposite if false, and if we will ask this: 2 (X == C) == (C == 0) we will know if C is truth teller or liar.
YAY – now we know if C (in our assumption) is 0 or 1. Let’s assume it is 0 – so t1[C] will return the expression as is. Now we need to understand who is 1 and who is 2 (from A and B). OK – so 2 (X == C) == (B == 2) will return whether or not B is 2 – and that’s it (A is not B and not C) J now we asked 3 questions and we know what is A, what is B and what is C – AKA m1 J