#first 3 lines are given
import random
seed = 1000
random.seed(seed)
#unscramble the rest
def recur(lst):
l2[i] = (l[i]*5+(l2[i]+n)*l[i])%l[i]
l2[i] += inp[i]
flag = ""
flag+=chr((l4[i]^l3[i]))
return flag
l.append(random.randint(6, 420))
l3[0] = l2[0]%mod
for i in range(1, n):
def decrypt(inp):
for i in range(n):
assert(len(l)==n)
return lst[0]
l = []
main()
def main():
l4 = [70, 123, 100, 53, 123, 58, 105, 109, 2, 108, 116, 21, 67, 69, 238, 47, 102, 110, 114, 84, 83, 68, 113, 72, 112, 54, 121, 104, 103, 41, 124]
l3[i] = (l2[i]^((l[i]&l3[i-1]+(l3[i-1]*l[i])%mod)//2))%mod
if(len(lst)==1):
assert(lst[0]>0)
for i in range(1, n):
for i in range(n):
return recur(lst[::2])/recur(lst[1::2])
print("flag is:", decrypt(inp))
l2[0] +=int(recur(l2[1:])*50)
l2 = [0]*n
flag_length = 31
mod = 256
print(l2)
n = len(inp)
inp = [1]*flag_length
l3 =[0]*n
- Separate the
def
and match thereturn
calls to the corresponding functions. - All lines containing
lst
belong torecur(lst)
function. flag
,l
,l2
,l3
,l4
are related to each other --> They are in the same functiondecrypt(inp)
.- Some lists assign the 0-th element separately. Use this to determine the correct
for
loop traversals (range(n)
orrange(1, n)
).
#first 3 lines are given
import random
seed = 1000
random.seed(seed)
#unscramble the rest
def recur(lst):
if(len(lst)==1):
assert(lst[0]>0)
return lst[0]
return recur(lst[::2])/recur(lst[1::2])
def decrypt(inp):
mod = 256
n = len(inp)
l = []
for i in range(n):
l.append(random.randint(6, 420))
assert(len(l)==n)
l2 = [0]*n
for i in range(1, n):
l2[i] = (l[i]*5+(l2[i]+n)*l[i])%l[i]
l2[i] += inp[i]
l2[0] +=int(recur(l2[1:])*50)
l3 =[0]*n
l3[0] = l2[0]%mod
for i in range(1, n):
l3[i] = (l2[i]^((l[i]&l3[i-1]+(l3[i-1]*l[i])%mod)//2))%mod
l4 = [70, 123, 100, 53, 123, 58, 105, 109, 2, 108, 116, 21, 67, 69, 238, 47, 102, 110, 114, 84, 83, 68, 113, 72, 112, 54, 121, 104, 103, 41, 124]
flag = ""
for i in range(n):
flag+=chr((l4[i]^l3[i]))
return flag
def main():
flag_length = 31
inp = [1]*flag_length
print("flag is:", decrypt(inp))
main()
tjctf{unshuffling_scripts_xdfj}
//Unimportant lines omitted
char *flag = "8.'8*{;8m33[o[3[3[%\")#*\\}";
//Unimportant lines omitted
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+8h] [rbp-58h]
int v5; // [rsp+Ch] [rbp-54h]
char s[32]; // [rsp+10h] [rbp-50h] BYREF
char dest[40]; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v8; // [rsp+58h] [rbp-8h]
v8 = __readfsqword(0x28u);
printf("Enter text: ");
fgets(s, 32, _bss_start);
s[strlen(s) - 1] = 0;
strcpy(dest, s);
v5 = strlen(s);
for ( i = 0; i < v5; ++i )
{
if ( s[i] <= 96 || s[i] > 122 )
{
if ( s[i] <= 64 || s[i] > 90 )
{
if ( s[i] <= 47 || s[i] > 52 )
{
if ( s[i] <= 52 || s[i] > 57 )
{
if ( s[i] != 123 && s[i] != 125 )
{
puts("wtmoo is this guess???");
printf("%c\n", (unsigned int)s[i]);
return 1;
}
}
else
{
s[i] -= 21;
}
}
else
{
s[i] += 43;
}
}
else
{
s[i] += 32;
}
}
else
{
s[i] -= 60;
}
}
if ( !strcmp(s, flag) )
printf(cow, dest);
else
printf(cow, s);
return 0;
}
- This is some sort of substitution cipher (or one-to-one mapping) where each character in the string is replaced by another character.
- The characters are independent of each other, i.e. changing 1 character in the original string does not affect the remaining characters in the resulting string.
- Run the algorithm above with
s
as the string0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
. The encoded string becomes[\]^_ !"#$abcdefghijklmnopqrstuvwxyz%&'()*+,-./0123456789:;<=>
. - Map each character in
8.'8*{;8m33[o[3[3[%")#*\}
to the decoded string and obtain the flag.
#include <bits/stdc++.h>
using namespace std;
int main() {
int i;
int v5;
string flag = "8.'8*{;8m33[o[3[3[%\")#*\\}";
string s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
string s2 = s;
v5 = 62;
for ( i = 0; i < v5; ++i )
{
if ( s[i] <= 96 || s[i] > 122 )
{
if ( s[i] <= 64 || s[i] > 90 )
{
if ( s[i] <= 47 || s[i] > 52 )
{
if ( s[i] <= 52 || s[i] > 57 )
{
if ( s[i] != 123 && s[i] != 125 )
{
puts("wtmoo is this guess???");
printf("%c\n", (unsigned int)s[i]);
return 1;
}
}
else
{
s[i] -= 21;
}
}
else
{
s[i] += 43;
}
}
else
{
s[i] += 32;
}
}
else
{
s[i] -= 60;
}
}
//Decoding flag string
for(i = 0; i < 26; i++) {
for(int j = 0; j < 62; j++) {
if(flag[i] == s[j]) {
flag[i] = s2[j];
break;
}
}
}
cout << flag;
return 0;
}
tjctf{wtMoo0O0o0o0a7e8f1}
def op1(b):
for i in range(len(b)):
b[i] += 8*(((b[i] % 10)*b[i]+75) & 1)
cur = 1
for j in range(420):
cur *= (b[i]+j) % 420
return b
def op2(b):
for i in range(len(b)):
for j in range(100):
b[i] = b[i] ^ 69
b[i] += 12
return b
def op3(b):
for i in range(len(b)):
b[i] = ((b[i] % 2) << 7)+(b[i]//2)
return b
def recur(b):
if len(b) == 1:
return b
assert len(b) % 3 == 0
a = len(b)
return op1(recur(b[0:a//3]))+op2(recur(b[a//3:2*a//3]))+op3(recur(b[2*a//3:]))
flag = ""
b = bytearray()
b.extend(map(ord, flag))
res = recur(b)
if res == b'\x8c\x86\xb1\x90\x86\xc9=\xbe\x9b\x80\x87\xca\x86\x8dKJ\xc4e?\xbc\xdbC\xbe!Y \xaf':
print("correct")
else:
print("oopsies")
-
For
op1(b)
:b[i] += 8*(((b[i] % 10)*b[i]+75) & 1)
can be intepreted asb[i] += 8 if b[i] is even; else b[i] += 0
.- The
cur
is just a distraction since the function returnsb
notcur
, andcur
is not used elsewhere. - How to reverse:
b[i] -= 8 if b[i] is even; else continue
.
-
For
op2(b)
:- The
for
loop is redundant becauseb[i] ^ 69 ^ 69 = b[i]
, so xor-ingb[i]
100 times with 69 does not changeb[i]
at all. - How to reverse:
b[i] -= 12
.
- The
-
For
op3(b)
:- The 8th bit (counting from right to left) of the updated
b[i]
stores the parity of the originalb[i]
, i.e. (0 if even and 1 if odd). - How to reverse:
b[i] >> 7
gives the 8th bit ofb[i]
, andb[i] & ((1 << k) - 1)
turns off the(k+1)-th
bit ofb[i]
.
- The 8th bit (counting from right to left) of the updated
-
For
recur(b)
:- The 3 operations above are initially applied to
b
character-by-character, then groups of 3, groups of 9, etc. - How to reverse: Apply the operations to the whole string, only then start recurring with smaller strings. So
recur(op1(b[0:a//3]))
will reverse the effect ofop1(recur(b[0:a//3]))
in the original code (op2
andop3
are also similar).
- The 3 operations above are initially applied to
def op1(b):
for i in range(len(b)):
b[i] -= 8 * (1 - (b[i] & 1))
return b
def op2(b):
for i in range(len(b)):
b[i] -= 12
return b
def op3(b):
for i in range(len(b)):
b[i] = (b[i] >> 7) + (b[i] & ((1 << 7)-1)) * 2
return b
def recur(b):
if len(b) == 1:
return b
assert len(b) % 3 == 0
a = len(b)
return recur(op1(b[0:a//3]))+recur(op2(b[a//3:2*a//3]))+recur(op3(b[2*a//3:]))
b = bytearray(b'\x8c\x86\xb1\x90\x86\xc9=\xbe\x9b\x80\x87\xca\x86\x8dKJ\xc4e?\xbc\xdbC\xbe!Y \xaf')
flag = recur(b).decode('utf-8')
print(flag)
tjctf{randomfifteenmorelet}
//Unimportant lines ommitted
_BYTE flag[29] =
{
18,
17,
0,
21,
11,
72,
60,
18,
12,
68,
0,
16,
81,
25,
46,
22,
3,
28,
66,
17,
10,
74,
114,
86,
13,
122,
116,
79,
0
};
//Unimportant lines ommitted
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-64h]
char s[72]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v6; // [rsp+58h] [rbp-18h]
v6 = __readfsqword(0x28u);
puts("Enter your flag");
fgets(s, 64, _bss_start);
if ( strlen(s) == 33 )
{
for ( i = 4; i < strlen(s) - 1; ++i )
{
if ( ((unsigned __int8)s[i - 4] ^ (unsigned __int8)s[i]) != flag[i - 4] )
{
puts("you're def wrong smh");
return 1;
}
}
puts("you might be right??? you might be wrong.... who knows?");
return 0;
}
else
{
puts("bad");
return 1;
}
}
- The flag is 33 characters long (not so important but useful when writing script).
- The program is checking whether
s[i-4] ^ s[i] == flag[i-4]
, starting from the 4th character (0-indexed). - The flag always starts with
tjctf{
, which means we can construct the whole flag using the fact thats[i] = flag[i-4] ^ s[i-4]
.
a = [18,
17,
0,
21,
11,
72,
60,
18,
12,
68,
0,
16,
81,
25,
46,
22,
3,
28,
66,
17,
10,
74,
114,
86,
13,
122,
116,
79,
0]
flag = 'tjctf{'
for i in range(6, 33):
flag += chr(ord(flag[i-4]) ^ a[i-4])
print(flag)
tjctf{cam3_saw_c0nqu3r3d98A24B5}
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+0h] [rbp-230h]
int i; // [rsp+4h] [rbp-22Ch]
unsigned __int64 v6; // [rsp+8h] [rbp-228h]
unsigned __int64 v7; // [rsp+10h] [rbp-220h]
FILE *stream; // [rsp+18h] [rbp-218h]
char s2[256]; // [rsp+20h] [rbp-210h] BYREF
char s[264]; // [rsp+120h] [rbp-110h] BYREF
unsigned __int64 v11; // [rsp+228h] [rbp-8h]
v11 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
type_text("last night, I had a dream...\ntaylor sw1ft, the dollar store version, appeared!\n");
prompt("what should I do? ", s2, 256);
if ( strcmp("sing", s2) )
{
puts("no, no, that's a bad idea.");
exit(0);
}
prompt("that's a great idea!\nI started to sing the following lyrics: ", s2, 256);
if ( strcmp("maybe I asked for too [many challenges to be written]", s2) )
{
puts("no, that's a dumb lyric.");
exit(0);
}
type_text("ok... that's a weird lyric but whatever\n");
prompt("that leads me to ask... how many challenges did you ask for??? ", s2, 256);
v6 = atol(s2);
if ( 35 * (((3 * v6) ^ 0xB6D8) % 0x521) % 0x5EB != 1370 )
{
type_text("that's a stupid number.\n");
exit(0);
}
prompt("ok yeah you're asking too much of everyone; try to lower the number??? ", s2, 256);
v7 = atol(s2);
if ( (35 * ((5 * v7 % 0x1E61) | 0x457) - 5) % 0x3E8 != 80 )
{
type_text("yeah.");
exit(0);
}
if ( v6 % v7 != 20202020 || v7 * v6 != 0x33D5D816326AADLL )
{
type_text("ok but they might think that's too much comparatively, duh.\n");
exit(0);
}
type_text("that's a lot more reasonable - good on you!\n");
usleep(0x124F8u);
type_text("ok, now that we've got that out of the way, back to the story...\n");
type_text("taylor was like, \"wow, you're so cool!\", and I said, \"no, you're so cool!\"\n");
type_text("after that, we kinda just sat in silence for a little bit. I could kinda tell I was losing her attention, so ");
v4 = 0;
for ( i = 0; i <= 11; ++i )
{
prompt("what should I do next? ", s2, 256);
if ( !strcmp("ask her about some flags", s2) )
{
++v4;
}
else if ( !strcmp("ask her about her new album", s2) )
{
v4 *= v4;
}
else
{
if ( strcmp("ask her about her tour", s2) )
{
type_text("no, that's weird\n");
exit(0);
}
v4 += 22;
}
}
if ( v4 != 2351 )
{
type_text("taylor died of boredom\n");
exit(0);
}
type_text("taylor got sick and tired of me asking her about various topics, so she finally responded: ");
stream = fopen("flag.txt", "r");
if ( !stream )
{
type_text("no flag </3\n");
exit(0);
}
fgets(s, 256, stream);
type_text(s);
return 0;
}
type_text("last night, I had a dream...\ntaylor sw1ft, the dollar store version, appeared!\n");
prompt("what should I do? ", s2, 256);
if ( strcmp("sing", s2) )
{
puts("no, no, that's a bad idea.");
exit(0);
}
prompt("that's a great idea!\nI started to sing the following lyrics: ", s2, 256);
if ( strcmp("maybe I asked for too [many challenges to be written]", s2) )
{
puts("no, that's a dumb lyric.");
exit(0);
}
- For the first part, just input
sing
andmaybe I asked for too [many challenges to be written]
when prompted.
prompt("that leads me to ask... how many challenges did you ask for??? ", s2, 256);
v6 = atol(s2);
if ( 35 * (((3 * v6) ^ 0xB6D8) % 0x521) % 0x5EB != 1370 )
{
type_text("that's a stupid number.\n");
exit(0);
}
prompt("ok yeah you're asking too much of everyone; try to lower the number??? ", s2, 256);
v7 = atol(s2);
if ( (35 * ((5 * v7 % 0x1E61) | 0x457) - 5) % 0x3E8 != 80 )
{
type_text("yeah.");
exit(0);
}
if ( v6 % v7 != 20202020 || v7 * v6 != 0x33D5D816326AADLL )
{
type_text("ok but they might think that's too much comparatively, duh.\n");
exit(0);
}
- Notice that we need to find suitable
v6
andv7
such that eventuallyv6 % v7 == 20202020 && v7 * v6 == 0x33D5D816326AAD
to pass, which meansv6
andv7
are factors of0x33D5D816326AAD = 14590347874298541 = 3 x 3 x 29 x 37 x 409 x 11071 x 333667
. - To find
v6
andv7
, I used the following script which finds all possible values ofv6
andv7
that pass the conditions35 * (((3 * v6) ^ 0xB6D8) % 0x521) % 0x5EB == 1370
and(35 * ((5 * v7 % 0x1E61) | 0x457) - 5) % 0x3E8 != 80
respectively.
import itertools
factors = []
prime = [3, 3, 29, 37, 409, 11071, 333667]
for i in range(1, 8):
factors.append(list(map(list, itertools.combinations(prime, i))))
prod = []
for f in factors:
for combi in f:
tmp = 1
for j in combi:
tmp *= j
if tmp not in prod:
prod.append(tmp)
for i in prod:
if (35 * (((3 * i) ^ 0xB6D8) % 0x521) % 0x5EB == 1370) and ((35 * ((5 * (0x33D5D816326AAD // i) % 0x1E61) | 0x457) - 5) % 0x3E8 == 80):
print(i, 0x33D5D816326AAD//i)
- Running the script gives only one pair of
(v6, v7)
of(131313131, 111111111)
.
v4 = 0;
for ( i = 0; i <= 11; ++i )
{
prompt("what should I do next? ", s2, 256);
if ( !strcmp("ask her about some flags", s2) )
{
++v4;
}
else if ( !strcmp("ask her about her new album", s2) )
{
v4 *= v4;
}
else
{
if ( strcmp("ask her about her tour", s2) )
{
type_text("no, that's weird\n");
exit(0);
}
v4 += 22;
}
}
if ( v4 != 2351 )
{
type_text("taylor died of boredom\n");
exit(0);
}
- There are 3 operations applied to
v4
:+1
,+22
, andsquaring v4
, and we have to achieve2351
from0
in exactly 12 operations. - Using some "quick maths"
2351 = 48 ** 2 + 47 = (0 + 22 + 22 + 1 + 1 + 1 + 1) ** 2 + (22 + 22 + 1 + 1 + 1)
which uses exactly 12 operations. Hence one of the possible lists of input is
ask her about her tour
ask her about her tour
ask her about some flags
ask her about some flags
ask her about some flags
ask her about some flags
ask her about her new album
ask her about her tour
ask her about her tour
ask her about some flags
ask her about some flags
ask her about some flags
- Combine everything and we can obtain the flag. I recommend using
pwntools
to send the input.
import itertools
from pwn import *
#Pre-calculate v6 and v7 before connecting to server.
factors = []
prime = [3, 3, 29, 37, 409, 11071, 333667]
for i in range(1, 8):
factors.append(list(map(list, itertools.combinations(prime, i))))
prod = []
for f in factors:
for combi in f:
tmp = 1
for j in combi:
tmp *= j
if tmp not in prod:
prod.append(tmp)
v6, v7 = 0, 0
for i in prod:
if (35 * (((3 * i) ^ 0xB6D8) % 0x521) % 0x5EB == 1370) and ((35 * ((5 * (0x33D5D816326AAD // i) % 0x1E61) | 0x457) - 5) % 0x3E8 == 80):
v6 = i
v7 = 0x33D5D816326AAD // i
#for part 3
op = ['ask her about some flags', 'ask her about her new album', 'ask her about her tour']
responses = [2, 2, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0]
#connecting to the server
r = remote('tjc.tf', 31500)
#part 1
r.recvuntil(b'what should I do? ')
r.sendline(b'sing')
r.recvuntil(b'I started to sing the following lyrics: ')
r.sendline(b'maybe I asked for too [many challenges to be written]')
#part 2
r.recvuntil(b'that leads me to ask... how many challenges did you ask for??? ')
r.sendline(str(v6).encode('utf-8'))
r.recvuntil(b'ok yeah you\'re asking too much of everyone; try to lower the number??? ')
r.sendline(str(v7).encode('utf-8'))
#part 3
for i in range(12):
r.recvuntil(b'what should I do next? ')
r.sendline(str(op[responses[i]]).encode('utf-8'))
#get flag
r.recvuntil(b'taylor got sick and tired of me asking her about various topics, so she finally responded: ')
flag = r.recvline()
print(flag)
r.close()
tjctf{5h3_ju5t_w4nt5_t0_st4y_1n_th4t_l4vend3r_h4z3_3a17362a}