# UIUCTF 2024: tooooo fancy 😏 [rev] writeup Writeup author: fsharp ## Introduction For this year's UIUCTF, I played with the [`ADMinions` team](https://adminions.ca) 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`) ![uiuctf_2024_tooooo_fancy_second_blood](https://hackmd.io/_uploads/rkxWSwOP0.png) ## 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](https://wiki.tcl-lang.org/page/tbcload). The wiki is about a programming language called [Tcl](https://en.wikipedia.org/wiki/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](https://github.com/corbamico/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 `lit`s, which we can assume are string `lit`erals 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](https://core.tcl-lang.org/tclquadcode/wiki?name=Standard+Tcl+Bytecodes). We see that many of them are [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type))-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 `lit`erals. - `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](https://en.wikipedia.org/wiki/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](https://www.tcl.tk/man/tcl8.6.13/TclCmd/set.htm) 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: ```python 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: ```python 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: ```python 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!}`