Try   HackMD

UIUCTF 2024: tooooo fancy 😏 [rev] writeup

Writeup author: fsharp

Introduction

For this year's UIUCTF, I played with the ADMinions team and mainly worked on reverse engineering and cryptography challenges. A challenge I solved during the CTF is tooooo fancy 😏, which I got second blood on and found to be very fancy indeed.

Problem description

Author: spicypete

I really hope this challenge TiCkLes your fancy! It is my most cursed challenge yet.

(Files provided: main.tbc and Dockerfile)

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Initial analysis: What are we dealing with?

The Dockerfile sets up a Linux environment where you can run the main.tbc file. What kind of file is that? Let's dig around for answers.

The first several lines of main.tbc provide some information:

package ifneeded tbcload 1.7 [list load [file join /home/chal/teapot/lib/tbcload libtbcload1.7.so]];
lappend auto_path /opt/ActiveTcl-8.6/lib/tcllib1.18;
if {[catch {package require tbcload 1.7} err] == 1} {
    return -code error "[info script]: The TclPro ByteCode Loader is not available or does not support the correct version -- $err"
}
tbcload::bceval {
TclPro ByteCode 2 0 1.7 8.6
[... snip ...]

There's an if statement and a mention of ByteCode, so it seems to be some sort of program.

From the first line, the tbcload package is loaded. Looking it up on Google leads to a webpage from the Tcler's Wiki. The wiki is about a programming language called Tcl, and the webpage contains the following sentences:

tbcload, loads code obfuscated by tclcompiler, tclchecker, and procomp.

tbcload is for source protection of deployed programs. It appeared originally in TclPro, and is part of ActiveState's ActiveTcl and Tcl Dev Kit distributions.

At this point, we can guess that main.tbc is a Tcl program that has been obfuscated. A commented section of the Dockerfile confirms this:

# TO COMPILE, UNCOMMENT OUT THIS SECTION
# COPY main.tcl /home/chal/src/main.tcl
# RUN echo "package ifneeded compiler 1.7.1 [list load [file join /home/chal/tdk/lib/tclcompiler libtclcompiler1.7.1.so]];\npackage require compiler;\ncompiler::compile main.tcl main.tbc" >> compiler.tcl
# RUN tclsh compiler.tcl
# RUN (echo "package ifneeded tbcload 1.7 [list load [file join /home/chal/teapot/lib/tbcload libtbcload1.7.so]];\nlappend auto_path /opt/ActiveTcl-8.6/lib/tcllib1.18;"; cat main.tbc) > tmp.tbc && mv tmp.tbc main.tbc
# RUN sed -i 's/tbcload 1.6/tbcload 1.7/g' ./main.tbc

The section contains instructions on creating the main.tbc file from the main.tcl source code, which includes compiling main.tcl with tclcompiler.

So, the goal of this challenge is to reverse-engineer the obfuscated main.tbc Tcl program and recover the flag from it!

Decoding the main.tbc bytecodes

Part of the bytecode in main.tbc looks like this:

52 0 858 45 12 0 272 4 19 52 60 -1 -1
858
w0E<!-fSs!8^w!!8f!!!&E&s!5v!!!,!!!!>aaB(+Ki<!2Z/s!2/YQ#6G:3w5#9X!/lSs!88
o9v8^w!!8f!!!0J60%5v!!!,!!!!HHgC(8^w!!/K!!!2ylf%LLDu!5v!!!/!!!!5>,>!M,X<
!OFCbB,/LNwv*<<!JjJE'BJe`B/JHK%v*<<!N-,'(ON2,!qSB2!w6N<!f#xD(G5v!!h/7,!C
CVw!#0E<!Lhk>!Rk^qvWLHt!F3WW!iS_w!<YUK(8^w!!/K!!!=jB?)R^Du!74WW!&!!!!@%2
?!t_J6,`Y(Qk5v!!!Dv!!!NWt/&6uo7-G_w!!/Lw!!@*w!*R^Du!684,!DI_w!#0E<!Lhk>!
ypn.&Zu(O%G5v!!+qK6,(`46#ON2,!.oAv!#0E<!bP's!b62bB9OX|(v*<<!WNGB(Q7M?!ho
ur*cdq*0ReG9vLWA9v8^w!!C3%!!=jB?)5v!!!7v!!!MQk/&dyWT+eero+W*OH&xr%p+BW8s
!M#J6,jy&-8-W8s!JR_w!Jw=XvEC_?!%FmcBNb>H/v*<<!I@1bBAB29+v*<<!Bb#>!ha7@!i
g@@!ha7@!mWt/&3FI^:OB:C(8^w!!MPv!!J5ON-5v!!!A!!!!J?P/&dyWT+^&_Z)SC_?!dC_
?!^bgm#ON2,!39u+!#0E<!`u2,!A7Dw!#0E<!a#<9+hb<9+J%w>!ha7@!m6XXv6lG8-m^w!!
Uhv!!J5ON-5v!!!I!!!!Bdy.&gvTQ,kFki-h+ol,j=PN-SC_?!hC_?!b%7n#_Sa<!<N2,!oL
<)!w6N<!k#xD(9v%fBD|.6,v*<<!L3b|(MaNH&y#/p+UR_w!Hjd!vD!!!!V.f`B.A-0%v*<<
!`Ei?!Q|(0&3K|+0gOH9v8^w!!.H!!!>s|Z)G?W<!y4mf%0<9!s7yb>!<N2,!x)1'!#0E<!J
^w!!OVv!!8=e`'SC_?!lC_?!l#<9+mq<9+5v!!!1!!!!@XJ.&DE4_:OB:C(8^w!!.H!!!:OF
B(G?W<!X4mf%0<9!sCS4I!<N2,!@1;w!#0E<!|s+'(*/C=!X|KtvNk0=!YcTtv#!!

They're a mess! Luckily, someone made a program called 'tbcload', which decodes these bytecodes and turns them into a format we can understand more easily. On a Windows computer, we can run tbcload-windows-amd64.exe decompile --detail main.tbc to generate the disassembly of main.tbc that we can further analyze.

The disassembly looks like this:

[... a bunch of hex characters, snip ...]
        Command 0,pc= 0-8
        (0)push1 0
        (2)push1 1
        (4)push1 2
        (6)invokeStk1 3
        (8)pop
        Command 1,pc= 9-32
        (9)startCommand 23 1
        (18)push1 3
[... snip ...]
[lit-0000]package
[lit-0001]require
[lit-0002]md5
[lit-0003]input
[lit-0004]
[lit-0005]puts
[lit-0006]Enter flag:
[... snip ...]

Understanding the disassembly

The bottom section of the disassembly contains many lits, which we can assume are string literals used by the program. Above this section are the disassembly of 52 commands, with bytecodes like push1 0 and invokeStk1 3. The number in the brackets preceding each bytecode indicates their location.

Searching for the bytecodes lands us on a webpage that describes what they do. We see that many of them are stack-based, where the execution of a bytecode leads to a stack being manipulated.

There are 19 kinds of bytecodes used in the challenge program. Here's my understanding of what they do.

General operations:

  • push1 <i>: Push an object at the i-th index of the 'object array' onto the stack. In the challenge, i is always between 0 and 44, so we can guess that the 'object array' is the 45 literals / variables at the end of the disassembly, and i refers to one of the literals.
  • pop: Remove the object at the top of the stack.
  • done: Stop executing the bytecodes.
  • startCommand <num1> <num2>: Mark the start of a command. This can be safely ignored in this challenge.

Comparison operations:

  • lt: Push the result of (2nd-topmost stack object < topmost stack object) onto the stack. The result will be logical, i.e. true or false.
  • eq: Push the result of (2nd-topmost stack object == topmost stack object) onto the stack.
  • ge: Push the result of (2nd-topmost stack object >= topmost stack object) onto the stack.

Jump operations:

  • jump1 <num>: Jump to the bytecode located at pc + num. pc stands for 'program counter' and is used by the Tcl shell to keep track of which bytecode is being executed. So, if there's a (123)jump1 456 bytecode and we reach it, the shell will jump to the bytecode at 123 + 456 = 579.
  • jump4 <num>: Behaves the same as jump1. The 1 and 4 suffixes refer to the number of bytes used to store num.
  • jumpTrue1 <num>: If the topmost stack object is true, jump to pc + num.
  • jumpTrue4 <num>: Behaves the same as jumpTrue1.
  • jumpFalse1 <num>: If the topmost stack object is false, jump to pc + num.

Reading and writing variables:

  • loadStk: Load the variable referred by the topmost stack object into the stack. What this means is that we can consider push1 <i> to put the name of the i-th variable from the object array onto the stack, and loadStk retrieves the actual value of that variable using its name.
  • storeStk: Set the value of the variable referred by the 2nd-topmost stack object to the topmost stack object.
  • incrStkImm <num>: Increment the value of the variable referred by the topmost stack object by num.
  • listIndex: Push a list element onto the stack, with the 2nd-topmost stack object referring to the list, and the topmost stack object being the list index.

Invoke / Eval operations:

  • exprStk: Evaluate the topmost stack object as an expression. For example, if the expression is the string 1 + 2 * 3, then this bytecode turns the topmost stack object into the number 7.
  • invokeStk1 <num>: Pop num objects from the stack and execute a Tcl command, with the first object as the command name and the remaining objects as arguments to the command. For instance, if the top 2 objects are puts and the string wow respectively, then invokeStk1 2 executes puts "wow".

String operations:

  • strcat <num>: Push the concatenation of the top num stack objects onto the stack.

With all of this in mind, we can reverse the disassembly.

A peculiarity with sets

By carefully keeping track of the state of the stack for each bytecode, we'll notice things we usually see in a program, such as if statements and for loops.

Something in particular stood out to me when I first reversed the disassembly:

[lit-0020]
  set set [md5::md5 -hex $set]
  set $set $wat

The set Tcl command sets the value of a variable. In the literal above, there are two statements: The first sets set to the hexadecimal MD5 digest of itself, while the second statement sets $set to the variable wat. However, $set is NOT the same as set, with the former being the value of the variable called set and the latter being the name of that variable.

Doesn't the second statement look a bit strange? How is it possible to set the value of the value of a variable to something else?

Let's try running some Tcl code to see what's going on.

set a 123
set b 456
set c "a"

puts $a
puts $b
puts $c

This prints 123, 456, and a. Now let's try doing what the second statement did:

set a 123
set b 456
set c "a"

set $c "wow" ;# <-- new!

puts $a
puts $b
puts $c

This time, the first line printed is wow, not 123. So, the second statement creates a new variable whose name is equal to the value of the set variable, and sets its value to the value of the wat variable.

Solving

The bytecode disassembly is equivalent to this Python script:

from hashlib import md5

input = input("Enter flag:\n").encode()

wtf = [492636, 406872, 481036, 456848, 489619, 458431, 466416, 482512, 413259, 424205, 476839, 441981, 489662, 490341, 510145, 428550, 480326, 499060, 417618, 486312, 452152, 470192, 476645, 496113, 433471, 403520, 503087, 467533, 508246, 477960, 367896, 408320, 423790, 439274, 525261, 462079]

definitely_not_flag_length = 36
set = "set"

wats = [254, 109, 126, 66, 220, 98, 230, 17, 83, 106, 123, 57, 214, 225, 96, 113, 126, 47, 73, 32, 174, 224, 111, 153, 83, 78, 253, 164, 96, 208, 68, 49, 55, 195, 2, 84, 39, 66, 84, 47, 248, 189, 176, 135, 105, 99, 124, 92, 180, 102, 97, 213, 118, 94, 155, 34, 225, 76, 168, 131, 106, 69, 64, 75, 162, 58, 138, 22, 205, 146, 228, 15, 235, 155, 253, 236, 158, 180, 70, 109, 115, 154, 208, 134, 14, 255, 244, 103, 203, 182, 199, 129, 43, 186, 101, 183, 25, 178, 212, 56, 49, 12, 18, 209, 1, 51, 172, 117, 233, 48, 56, 219, 177, 86, 3, 67, 139, 149, 217, 103, 233, 98, 3, 139, 3, 243, 248, 222, 6, 9, 87, 14, 230, 211, 121, 198, 140, 182, 27, 38, 145, 189, 110, 74, 99, 116, 99, 193, 47, 237, 16, 133, 104, 109, 86, 46, 15, 59, 40, 153, 45, 177, 0, 163, 157, 129, 211, 68, 158, 135, 93, 230, 60, 65, 116, 254, 204, 67, 44, 51, 135, 230, 88, 198, 141, 156, 108, 63, 84, 164, 119, 240, 224, 121, 168, 216, 116, 250, 191, 58, 237, 77, 8, 206, 134, 65, 222, 198, 40, 119, 202, 76, 204, 153, 234, 123, 241, 135, 13, 172, 251, 1, 247, 169, 206, 184, 215, 63, 225, 3, 17, 88, 238, 215, 197, 97, 50, 101, 130, 146, 46, 45, 185, 163, 18, 219, 174, 182, 247, 73, 209, 62, 195, 139, 154, 92, 248, 203, 138, 131, 45, 122, 186, 187, 151, 25, 27, 185, 110, 251, 3, 99, 206, 85, 187, 246, 176, 56, 215, 87, 100, 132, 69, 112, 56, 60, 137, 194, 229, 161, 69, 124, 137, 207, 251, 28, 66, 10, 228, 3, 176, 3, 23, 219, 183, 34, 37, 49, 87, 85, 153, 145, 186, 194, 45, 10, 136, 245, 224, 25, 99, 67, 115, 65, 7, 198, 99, 225, 167, 210, 220, 166, 133, 137, 168, 233, 68, 250, 215, 232, 149, 89, 50, 164, 44, 92, 227, 83, 81, 231, 127, 108, 61, 104, 245, 247, 247, 14, 206, 23, 62, 156, 117, 80, 240, 238, 152, 126, 143, 177, 208, 127, 238, 238, 84, 235, 239, 218, 117, 4, 149, 32, 167, 129, 98, 106, 210, 103, 90, 103, 163, 58, 166, 1, 232, 180, 145, 173, 22, 254, 53, 217, 74, 239, 128, 35, 113, 83, 94, 71, 39, 87, 245, 83, 118, 78, 29, 19, 37, 1, 133, 105, 81, 163, 77, 25, 97, 117, 221, 198, 186, 157, 81, 149, 144, 30, 183, 208, 158, 12, 90, 211, 144, 92, 38, 98, 7, 106, 165, 14, 113, 128, 224, 130, 230, 37, 175, 62, 189, 14, 61, 124, 105, 81, 37, 222, 147, 186, 6, 100, 218, 71, 77, 241, 78, 198, 224, 51, 97, 3, 128, 104, 126, 126, 77, 94, 165, 113, 52, 83, 2, 226, 237, 224, 14, 2, 91, 218, 110, 238, 160, 210, 183, 121, 32, 158, 3, 223, 233, 219, 247, 79, 112, 106, 219, 78, 111, 21, 158, 187, 84, 31, 70, 224, 135, 77, 189, 248, 216, 223, 27, 65, 16, 218, 129, 135, 37, 147, 255, 112, 191, 228, 120, 148, 89, 30, 211, 69, 113, 142, 184, 0, 89, 28, 65, 21, 188, 251, 28, 8, 40, 20, 182, 248, 95, 223, 110, 235, 39, 25, 175, 125, 204, 130, 223, 32, 96, 41, 233, 76, 42, 197, 3, 101, 191, 197, 178, 194, 218, 231, 211, 145, 147, 157, 201, 179, 22, 46, 130, 243, 49, 121, 253, 8, 240, 146, 163, 167, 154, 233, 91, 234, 114, 71, 105, 239, 71, 81, 122, 148, 237, 140, 26, 238, 123, 61, 39, 52, 207, 39, 126, 8, 154, 254, 208, 2, 55, 180, 3, 39, 179, 55, 64, 205, 199, 70, 29, 24, 38, 207, 74, 152, 32, 190, 252, 53, 115, 178, 111, 212, 179, 179, 201, 183, 113, 72, 0, 225, 30, 20, 23, 18, 60, 111, 187, 123, 212, 152, 19, 116, 153, 126, 6, 194, 231, 55, 239, 71, 244, 93, 10, 199, 248, 228, 144, 71, 174, 171, 191, 157, 42, 234, 15, 118, 3, 84, 49, 33, 253, 231, 34, 63, 12, 106, 26, 199, 168, 41, 156, 253, 143, 234, 180, 45, 196, 36, 103, 231, 233, 187, 201, 142, 148, 33, 213, 213, 8, 153, 76, 245, 145, 0, 22, 32, 174, 94, 121, 20, 214, 207, 60, 26, 228, 190, 101, 179, 191, 141, 100, 174, 6, 75, 155, 130, 149, 242, 84, 190, 28, 147, 66, 218, 212, 104, 160, 93, 47, 102, 40, 122, 136, 175, 229, 214, 181, 22, 192, 148, 159, 108, 153, 32, 187, 136, 80, 69, 173, 6, 127, 6, 120, 248, 228, 196, 160, 113, 137, 208, 103, 27, 174, 45, 21, 20, 253, 110, 183, 31, 159, 114, 163, 158, 69, 53, 20, 34, 205, 22, 253, 169, 253, 23, 93, 73, 68, 126, 124, 250, 169, 157, 198, 133, 199, 51, 217, 250, 81, 227, 153, 99, 40, 195, 181, 59, 76, 182, 151, 118, 94, 126, 213, 167, 209, 191, 161, 214, 38, 238, 228, 121, 238, 0, 122, 233, 19, 110, 152, 133, 115, 93, 211, 200, 119, 239, 112, 209, 231, 2, 163, 69, 235, 165, 64, 82, 37, 27, 174, 200, 46, 254, 9, 87, 217, 199, 132, 140, 85, 33, 152, 150, 232, 111, 84, 2, 7, 173, 241, 189, 166, 50, 230, 21, 44, 202, 150, 176, 79, 243, 123, 178, 27, 98, 105, 28, 186, 247, 101, 197, 240, 77, 220, 240, 253, 155, 201, 109, 174, 3, 215, 129, 109, 63, 250, 28, 88, 94, 174, 242, 237, 14, 165, 96, 241, 176, 199, 39, 143, 101, 107, 232, 33, 126, 133, 102, 231, 240, 86, 180, 113, 148, 98, 238, 156, 226, 1, 96, 25, 43, 223, 17, 97, 29, 107, 22, 47, 182, 211, 237, 126, 249, 22, 39, 63, 71, 141, 123, 214, 109, 77, 1, 121, 3, 215, 242, 99, 8, 15, 95, 159, 26, 14, 240, 67, 227, 249, 24, 14, 251, 11, 70, 227, 151, 69, 111, 168, 59, 236, 30, 148, 59, 74, 207, 205, 216, 13, 101, 81, 13, 151, 242, 199, 236, 24, 225, 108, 107, 155, 87, 17, 56, 97, 169, 66, 139, 163, 68, 82, 75, 90, 16, 170, 91, 39, 21, 191, 137, 104, 133, 16, 75, 190, 197, 234, 213, 177, 79, 195, 152, 32, 248, 0, 78, 127, 144, 170, 185, 146, 53, 113, 82, 83, 0, 214, 143, 151, 211, 206, 191, 185, 151, 185, 100, 191, 168, 160, 223, 185, 124, 183, 94, 14, 64, 112, 139, 162, 36, 239, 29, 217, 10, 187, 31, 1, 137, 123, 173, 21, 72, 117, 54, 248, 186, 29, 180, 88, 20, 81, 19, 242, 231, 207, 112, 166, 34, 28, 109, 55, 221, 115, 182, 180, 175, 66, 182, 31, 57, 77, 217, 68, 27, 71, 108, 239, 224, 227, 59, 12, 211, 17, 178, 49, 211, 42, 224, 84, 45, 23, 219, 4, 157, 42, 52, 151, 102, 141, 227, 163, 153, 246, 12, 231, 20, 89, 97, 39, 205, 212, 42, 58, 48, 174, 241, 146, 139, 35, 121, 93, 107, 32, 107, 126, 86, 61, 168, 204, 219, 219, 18, 100, 30, 49, 169, 102, 16, 219, 195, 5, 59, 18, 185, 214, 39, 123, 44, 41, 69, 151, 149, 52, 212, 77, 151, 152, 144, 122, 114, 254, 109, 162, 208, 55, 27, 193, 33, 64, 110, 127, 130, 186, 55, 21, 169, 131, 206, 99, 230, 117, 11, 36, 239, 40, 57, 62, 70, 222, 132, 90, 88, 16, 155, 118, 154, 180, 25, 176, 92]

md5_vars = {}
for wat in wats:
    set = md5(set.encode()).hexdigest()
    md5_vars[set] = wat

unfanciness = 0

for n in range(definitely_not_flag_length):
    set = md5(b"set").hexdigest()
    epic = 0
    for k in range(definitely_not_flag_length):
        if k == 0:
            for i in range(((n + 3) * n + 1) // 2):
                set = md5(set.encode()).hexdigest()
        epic += md5_vars[set] * input[definitely_not_flag_length - 1 - k]
        offset = n + k + 1
        if k >= (definitely_not_flag_length - n - 1):
            offset = definitely_not_flag_length * 2 - offset - 1
        for i in range(offset):
            set = md5(set.encode()).hexdigest()
    unfanciness |= (epic ^ wtf[n])

if unfanciness == 0:
    print("Wow, you must be quite fancy. Exquisite job!")
else:
    print("That's not very fancy. Throw it away!")

The goal is to enter a flag such that unfanciness is 0. This is only possible if epic ^ wtf[n] == 0.

For a particular n, epic can be expressed as a0 * flag[0] + a1 * flag[1] + ... + a35 * flag[35], and we know that it must be equal to wtf[n]. So, we have a 36 x 36 matrix A with values coming from wats, a vector b equal to wtf, and the equation Ax = b, where x is a vector representing the flag.

We can solve this equation using SageMath. Let's modify this Python script to print a Sage script that can recover the flag:

from hashlib import md5

wtf = [492636, 406872, 481036, 456848, 489619, 458431, 466416, 482512, 413259, 424205, 476839, 441981, 489662, 490341, 510145, 428550, 480326, 499060, 417618, 486312, 452152, 470192, 476645, 496113, 433471, 403520, 503087, 467533, 508246, 477960, 367896, 408320, 423790, 439274, 525261, 462079]

definitely_not_flag_length = 36
set = "set"

wats = [254, 109, 126, 66, 220, 98, 230, 17, 83, 106, 123, 57, 214, 225, 96, 113, 126, 47, 73, 32, 174, 224, 111, 153, 83, 78, 253, 164, 96, 208, 68, 49, 55, 195, 2, 84, 39, 66, 84, 47, 248, 189, 176, 135, 105, 99, 124, 92, 180, 102, 97, 213, 118, 94, 155, 34, 225, 76, 168, 131, 106, 69, 64, 75, 162, 58, 138, 22, 205, 146, 228, 15, 235, 155, 253, 236, 158, 180, 70, 109, 115, 154, 208, 134, 14, 255, 244, 103, 203, 182, 199, 129, 43, 186, 101, 183, 25, 178, 212, 56, 49, 12, 18, 209, 1, 51, 172, 117, 233, 48, 56, 219, 177, 86, 3, 67, 139, 149, 217, 103, 233, 98, 3, 139, 3, 243, 248, 222, 6, 9, 87, 14, 230, 211, 121, 198, 140, 182, 27, 38, 145, 189, 110, 74, 99, 116, 99, 193, 47, 237, 16, 133, 104, 109, 86, 46, 15, 59, 40, 153, 45, 177, 0, 163, 157, 129, 211, 68, 158, 135, 93, 230, 60, 65, 116, 254, 204, 67, 44, 51, 135, 230, 88, 198, 141, 156, 108, 63, 84, 164, 119, 240, 224, 121, 168, 216, 116, 250, 191, 58, 237, 77, 8, 206, 134, 65, 222, 198, 40, 119, 202, 76, 204, 153, 234, 123, 241, 135, 13, 172, 251, 1, 247, 169, 206, 184, 215, 63, 225, 3, 17, 88, 238, 215, 197, 97, 50, 101, 130, 146, 46, 45, 185, 163, 18, 219, 174, 182, 247, 73, 209, 62, 195, 139, 154, 92, 248, 203, 138, 131, 45, 122, 186, 187, 151, 25, 27, 185, 110, 251, 3, 99, 206, 85, 187, 246, 176, 56, 215, 87, 100, 132, 69, 112, 56, 60, 137, 194, 229, 161, 69, 124, 137, 207, 251, 28, 66, 10, 228, 3, 176, 3, 23, 219, 183, 34, 37, 49, 87, 85, 153, 145, 186, 194, 45, 10, 136, 245, 224, 25, 99, 67, 115, 65, 7, 198, 99, 225, 167, 210, 220, 166, 133, 137, 168, 233, 68, 250, 215, 232, 149, 89, 50, 164, 44, 92, 227, 83, 81, 231, 127, 108, 61, 104, 245, 247, 247, 14, 206, 23, 62, 156, 117, 80, 240, 238, 152, 126, 143, 177, 208, 127, 238, 238, 84, 235, 239, 218, 117, 4, 149, 32, 167, 129, 98, 106, 210, 103, 90, 103, 163, 58, 166, 1, 232, 180, 145, 173, 22, 254, 53, 217, 74, 239, 128, 35, 113, 83, 94, 71, 39, 87, 245, 83, 118, 78, 29, 19, 37, 1, 133, 105, 81, 163, 77, 25, 97, 117, 221, 198, 186, 157, 81, 149, 144, 30, 183, 208, 158, 12, 90, 211, 144, 92, 38, 98, 7, 106, 165, 14, 113, 128, 224, 130, 230, 37, 175, 62, 189, 14, 61, 124, 105, 81, 37, 222, 147, 186, 6, 100, 218, 71, 77, 241, 78, 198, 224, 51, 97, 3, 128, 104, 126, 126, 77, 94, 165, 113, 52, 83, 2, 226, 237, 224, 14, 2, 91, 218, 110, 238, 160, 210, 183, 121, 32, 158, 3, 223, 233, 219, 247, 79, 112, 106, 219, 78, 111, 21, 158, 187, 84, 31, 70, 224, 135, 77, 189, 248, 216, 223, 27, 65, 16, 218, 129, 135, 37, 147, 255, 112, 191, 228, 120, 148, 89, 30, 211, 69, 113, 142, 184, 0, 89, 28, 65, 21, 188, 251, 28, 8, 40, 20, 182, 248, 95, 223, 110, 235, 39, 25, 175, 125, 204, 130, 223, 32, 96, 41, 233, 76, 42, 197, 3, 101, 191, 197, 178, 194, 218, 231, 211, 145, 147, 157, 201, 179, 22, 46, 130, 243, 49, 121, 253, 8, 240, 146, 163, 167, 154, 233, 91, 234, 114, 71, 105, 239, 71, 81, 122, 148, 237, 140, 26, 238, 123, 61, 39, 52, 207, 39, 126, 8, 154, 254, 208, 2, 55, 180, 3, 39, 179, 55, 64, 205, 199, 70, 29, 24, 38, 207, 74, 152, 32, 190, 252, 53, 115, 178, 111, 212, 179, 179, 201, 183, 113, 72, 0, 225, 30, 20, 23, 18, 60, 111, 187, 123, 212, 152, 19, 116, 153, 126, 6, 194, 231, 55, 239, 71, 244, 93, 10, 199, 248, 228, 144, 71, 174, 171, 191, 157, 42, 234, 15, 118, 3, 84, 49, 33, 253, 231, 34, 63, 12, 106, 26, 199, 168, 41, 156, 253, 143, 234, 180, 45, 196, 36, 103, 231, 233, 187, 201, 142, 148, 33, 213, 213, 8, 153, 76, 245, 145, 0, 22, 32, 174, 94, 121, 20, 214, 207, 60, 26, 228, 190, 101, 179, 191, 141, 100, 174, 6, 75, 155, 130, 149, 242, 84, 190, 28, 147, 66, 218, 212, 104, 160, 93, 47, 102, 40, 122, 136, 175, 229, 214, 181, 22, 192, 148, 159, 108, 153, 32, 187, 136, 80, 69, 173, 6, 127, 6, 120, 248, 228, 196, 160, 113, 137, 208, 103, 27, 174, 45, 21, 20, 253, 110, 183, 31, 159, 114, 163, 158, 69, 53, 20, 34, 205, 22, 253, 169, 253, 23, 93, 73, 68, 126, 124, 250, 169, 157, 198, 133, 199, 51, 217, 250, 81, 227, 153, 99, 40, 195, 181, 59, 76, 182, 151, 118, 94, 126, 213, 167, 209, 191, 161, 214, 38, 238, 228, 121, 238, 0, 122, 233, 19, 110, 152, 133, 115, 93, 211, 200, 119, 239, 112, 209, 231, 2, 163, 69, 235, 165, 64, 82, 37, 27, 174, 200, 46, 254, 9, 87, 217, 199, 132, 140, 85, 33, 152, 150, 232, 111, 84, 2, 7, 173, 241, 189, 166, 50, 230, 21, 44, 202, 150, 176, 79, 243, 123, 178, 27, 98, 105, 28, 186, 247, 101, 197, 240, 77, 220, 240, 253, 155, 201, 109, 174, 3, 215, 129, 109, 63, 250, 28, 88, 94, 174, 242, 237, 14, 165, 96, 241, 176, 199, 39, 143, 101, 107, 232, 33, 126, 133, 102, 231, 240, 86, 180, 113, 148, 98, 238, 156, 226, 1, 96, 25, 43, 223, 17, 97, 29, 107, 22, 47, 182, 211, 237, 126, 249, 22, 39, 63, 71, 141, 123, 214, 109, 77, 1, 121, 3, 215, 242, 99, 8, 15, 95, 159, 26, 14, 240, 67, 227, 249, 24, 14, 251, 11, 70, 227, 151, 69, 111, 168, 59, 236, 30, 148, 59, 74, 207, 205, 216, 13, 101, 81, 13, 151, 242, 199, 236, 24, 225, 108, 107, 155, 87, 17, 56, 97, 169, 66, 139, 163, 68, 82, 75, 90, 16, 170, 91, 39, 21, 191, 137, 104, 133, 16, 75, 190, 197, 234, 213, 177, 79, 195, 152, 32, 248, 0, 78, 127, 144, 170, 185, 146, 53, 113, 82, 83, 0, 214, 143, 151, 211, 206, 191, 185, 151, 185, 100, 191, 168, 160, 223, 185, 124, 183, 94, 14, 64, 112, 139, 162, 36, 239, 29, 217, 10, 187, 31, 1, 137, 123, 173, 21, 72, 117, 54, 248, 186, 29, 180, 88, 20, 81, 19, 242, 231, 207, 112, 166, 34, 28, 109, 55, 221, 115, 182, 180, 175, 66, 182, 31, 57, 77, 217, 68, 27, 71, 108, 239, 224, 227, 59, 12, 211, 17, 178, 49, 211, 42, 224, 84, 45, 23, 219, 4, 157, 42, 52, 151, 102, 141, 227, 163, 153, 246, 12, 231, 20, 89, 97, 39, 205, 212, 42, 58, 48, 174, 241, 146, 139, 35, 121, 93, 107, 32, 107, 126, 86, 61, 168, 204, 219, 219, 18, 100, 30, 49, 169, 102, 16, 219, 195, 5, 59, 18, 185, 214, 39, 123, 44, 41, 69, 151, 149, 52, 212, 77, 151, 152, 144, 122, 114, 254, 109, 162, 208, 55, 27, 193, 33, 64, 110, 127, 130, 186, 55, 21, 169, 131, 206, 99, 230, 117, 11, 36, 239, 40, 57, 62, 70, 222, 132, 90, 88, 16, 155, 118, 154, 180, 25, 176, 92]

md5_vars = {}
for wat in wats:
    set = md5(set.encode()).hexdigest()
    md5_vars[set] = wat

matrix = [[0 for i in range(definitely_not_flag_length)] for j in range(definitely_not_flag_length)]

for n in range(definitely_not_flag_length):
    set = md5(b"set").hexdigest()
    for k in range(definitely_not_flag_length):
        if k == 0:
            for i in range(((n + 3) * n + 1) // 2):
                set = md5(set.encode()).hexdigest()
        matrix[n][definitely_not_flag_length - 1 - k] = md5_vars[set]
        offset = n + k + 1
        if k >= (definitely_not_flag_length - n - 1):
            offset = definitely_not_flag_length * 2 - offset - 1
        for i in range(offset):
            set = md5(set.encode()).hexdigest()

print(f"A = matrix({matrix})\n")
print(f"b = vector({wtf})\n")
print("flag = bytes(A.solve_right(b)).decode()")
print("print(flag)")

This gives us the following Sage script:

A = matrix([[126, 179, 20, 216, 91, 222, 30, 113, 117, 108, 198, 176, 176, 139, 88, 202, 119, 230, 109, 140, 233, 51, 129, 70, 138, 34, 99, 39, 96, 224, 113, 123, 230, 66, 109, 254], [0, 8, 22, 182, 223, 218, 147, 183, 83, 4, 61, 99, 3, 56, 154, 238, 76, 240, 60, 86, 182, 98, 172, 43, 109, 22, 225, 124, 66, 208, 111, 126, 57, 17, 220, 126], [234, 225, 154, 46, 248, 27, 110, 186, 208, 94, 149, 104, 225, 23, 215, 92, 215, 204, 224, 65, 46, 27, 3, 117, 186, 115, 205, 76, 92, 84, 68, 153, 47, 214, 83, 98], [213, 15, 30, 254, 130, 95, 65, 238, 6, 158, 71, 32, 245, 167, 219, 87, 248, 197, 153, 121, 116, 15, 38, 139, 233, 101, 154, 146, 168, 180, 47, 49, 83, 73, 225, 106], [28, 8, 118, 20, 208, 243, 223, 16, 160, 100, 12, 39, 167, 247, 210, 183, 100, 203, 97, 234, 168, 254, 59, 145, 3, 48, 183, 208, 228, 131, 102, 248, 55, 78, 32, 96], [120, 147, 153, 3, 23, 2, 49, 110, 218, 210, 218, 90, 87, 129, 247, 220, 34, 132, 138, 50, 123, 216, 204, 40, 189, 243, 56, 25, 134, 15, 106, 97, 189, 195, 253, 174], [23, 248, 66, 76, 84, 18, 55, 121, 235, 129, 183, 71, 211, 245, 98, 14, 166, 37, 69, 131, 101, 241, 116, 67, 153, 110, 248, 219, 178, 14, 235, 69, 213, 176, 2, 164], [167, 93, 228, 218, 245, 49, 60, 180, 253, 39, 135, 121, 77, 144, 83, 106, 206, 133, 49, 112, 45, 130, 135, 250, 44, 45, 74, 222, 177, 212, 255, 155, 64, 118, 135, 84], [235, 209, 73, 196, 212, 145, 33, 111, 3, 8, 25, 37, 32, 241, 92, 118, 210, 23, 137, 87, 56, 122, 146, 13, 191, 51, 177, 99, 6, 86, 56, 244, 253, 75, 94, 105], [166, 165, 191, 68, 160, 104, 0, 253, 187, 39, 240, 175, 147, 158, 78, 38, 78, 103, 62, 168, 85, 60, 186, 46, 172, 58, 135, 0, 116, 9, 3, 49, 103, 236, 162, 155], [109, 50, 64, 161, 126, 113, 160, 22, 231, 123, 179, 146, 125, 255, 3, 198, 98, 29, 90, 156, 233, 153, 137, 187, 45, 251, 237, 230, 163, 99, 87, 67, 12, 203, 158, 58], [126, 174, 230, 82, 214, 124, 137, 93, 32, 34, 212, 55, 163, 204, 112, 223, 224, 7, 19, 103, 117, 68, 145, 194, 151, 185, 1, 77, 88, 157, 193, 14, 139, 18, 182, 180], [211, 133, 3, 21, 37, 38, 250, 208, 47, 174, 63, 152, 64, 167, 130, 191, 233, 51, 106, 37, 163, 80, 250, 186, 229, 25, 163, 247, 8, 198, 129, 47, 230, 149, 209, 199], [14, 237, 102, 215, 44, 27, 238, 169, 103, 102, 94, 12, 19, 205, 154, 223, 228, 219, 97, 165, 1, 58, 240, 215, 194, 161, 27, 18, 169, 206, 141, 211, 237, 211, 217, 1], [216, 240, 126, 231, 129, 202, 174, 228, 157, 27, 40, 121, 106, 116, 199, 233, 32, 120, 247, 3, 14, 133, 166, 238, 232, 45, 69, 185, 219, 206, 134, 156, 68, 16, 121, 103], [68, 13, 67, 249, 240, 109, 150, 200, 121, 198, 174, 122, 20, 26, 153, 70, 91, 96, 148, 79, 128, 113, 105, 1, 152, 149, 10, 124, 110, 174, 184, 65, 108, 158, 133, 198], [195, 82, 101, 227, 22, 86, 63, 176, 46, 238, 133, 45, 136, 214, 199, 126, 29, 234, 41, 89, 112, 104, 128, 81, 232, 126, 89, 136, 137, 251, 182, 215, 222, 63, 135, 104], [206, 152, 75, 81, 249, 39, 180, 250, 79, 254, 0, 199, 21, 175, 207, 168, 6, 24, 114, 233, 30, 106, 126, 224, 163, 180, 143, 50, 245, 207, 3, 247, 63, 198, 84, 93], [36, 191, 32, 90, 13, 24, 63, 113, 28, 243, 9, 122, 51, 20, 229, 60, 41, 194, 38, 71, 76, 211, 219, 126, 130, 77, 145, 177, 164, 224, 251, 99, 73, 225, 40, 164], [180, 239, 185, 248, 16, 151, 14, 71, 148, 88, 123, 87, 233, 217, 253, 214, 26, 156, 231, 207, 105, 42, 69, 78, 77, 230, 25, 173, 208, 44, 25, 28, 206, 209, 3, 119], [180, 88, 29, 151, 0, 170, 242, 251, 141, 98, 94, 178, 217, 19, 250, 110, 181, 228, 253, 55, 74, 239, 197, 113, 111, 94, 37, 97, 22, 127, 92, 99, 66, 85, 62, 17], [12, 175, 20, 217, 185, 78, 91, 199, 11, 123, 238, 174, 27, 199, 110, 81, 183, 22, 190, 143, 239, 152, 71, 3, 142, 21, 165, 175, 117, 254, 238, 227, 67, 10, 187, 195], [52, 211, 66, 81, 10, 100, 127, 39, 236, 70, 214, 156, 242, 98, 132, 152, 227, 31, 192, 101, 234, 71, 32, 81, 101, 184, 158, 113, 62, 221, 53, 238, 83, 115, 228, 246], [205, 151, 17, 182, 19, 187, 191, 144, 21, 24, 227, 109, 226, 237, 105, 140, 133, 153, 159, 148, 179, 180, 244, 190, 122, 191, 0, 187, 52, 189, 198, 217, 84, 81, 65, 3], [32, 212, 102, 178, 31, 242, 31, 168, 170, 191, 225, 151, 77, 1, 14, 28, 85, 115, 99, 114, 159, 191, 45, 93, 252, 148, 197, 89, 84, 83, 14, 186, 74, 235, 231, 7], [49, 107, 42, 141, 49, 57, 231, 1, 160, 185, 137, 108, 69, 1, 96, 165, 186, 33, 93, 40, 163, 108, 141, 196, 10, 53, 237, 178, 28, 31, 2, 61, 157, 239, 239, 127], [39, 169, 126, 58, 227, 211, 77, 207, 137, 223, 146, 104, 107, 111, 121, 25, 96, 247, 152, 211, 195, 158, 153, 100, 36, 199, 115, 140, 194, 65, 70, 226, 124, 81, 128, 218], [151, 123, 102, 86, 48, 163, 42, 217, 112, 123, 185, 53, 133, 155, 168, 3, 43, 241, 101, 150, 200, 181, 69, 32, 174, 103, 248, 178, 26, 218, 21, 224, 237, 105, 149, 35], [55, 152, 44, 16, 61, 174, 153, 224, 68, 166, 173, 124, 113, 16, 87, 59, 215, 223, 176, 197, 232, 119, 59, 53, 187, 6, 231, 228, 111, 238, 231, 188, 135, 224, 81, 144], [186, 27, 144, 41, 219, 168, 241, 246, 84, 27, 34, 21, 183, 82, 75, 17, 236, 242, 17, 199, 240, 111, 239, 76, 20, 136, 75, 233, 144, 212, 123, 211, 251, 77, 14, 37], [230, 55, 193, 122, 69, 195, 204, 146, 12, 45, 71, 28, 72, 94, 83, 190, 56, 30, 99, 97, 39, 77, 84, 112, 182, 34, 80, 155, 187, 71, 179, 61, 145, 28, 189, 2], [57, 117, 21, 33, 114, 151, 5, 219, 139, 231, 23, 108, 109, 117, 14, 0, 197, 97, 148, 8, 29, 143, 220, 2, 209, 151, 205, 69, 130, 201, 174, 179, 39, 147, 8, 248], [90, 62, 11, 169, 64, 254, 149, 59, 219, 35, 20, 219, 239, 55, 54, 64, 214, 234, 169, 59, 15, 107, 101, 240, 7, 231, 118, 22, 173, 149, 142, 171, 201, 52, 157, 40], [118, 88, 70, 36, 131, 110, 109, 52, 18, 18, 121, 89, 4, 224, 221, 248, 112, 143, 213, 66, 74, 95, 22, 107, 253, 173, 2, 94, 253, 6, 242, 148, 191, 183, 207, 201], [25, 154, 16, 222, 239, 206, 127, 162, 212, 185, 100, 93, 97, 157, 227, 115, 186, 139, 151, 177, 139, 207, 159, 47, 232, 155, 241, 163, 126, 169, 127, 84, 33, 157, 113, 39], [92, 176, 180, 155, 132, 40, 99, 130, 208, 77, 214, 30, 107, 39, 42, 59, 182, 29, 162, 211, 79, 163, 205, 26, 182, 33, 201, 189, 69, 213, 253, 6, 190, 213, 42, 72]])

b = vector([492636, 406872, 481036, 456848, 489619, 458431, 466416, 482512, 413259, 424205, 476839, 441981, 489662, 490341, 510145, 428550, 480326, 499060, 417618, 486312, 452152, 470192, 476645, 496113, 433471, 403520, 503087, 467533, 508246, 477960, 367896, 408320, 423790, 439274, 525261, 462079])

flag = bytes(A.solve_right(b)).decode()
print(flag)

Running it, we get the flag: uiuctf{1_hope_that_tcls_y0ur_f4ncy!}