Easy misc/web challenges.

7110

Information

  • category : misc
  • points : 58

Description

Santa is stranded on the Christmas Islands and is desperately trying to reach his trusty companion via cellphone. We’ve bugged the device with a primitive keylogger and have been able to decode some of the SMS, but couldn’t make much sense of the last one. Can you give us a hand?

1 file.tar.gz

Writeup

The tar file contains 4 csv files, 3 txt files, and 1 header file (keys.h). The txt files corresponds to the decoded csv file and our scope is to decode the 4th csv.

keys.h:

#ifndef N7110
#define N7110

enum {
    N7110_KEYPAD_ZERO = 0,
    N7110_KEYPAD_ONE = 1,
    N7110_KEYPAD_TWO = 2,
    N7110_KEYPAD_THREE = 3,
    N7110_KEYPAD_FOUR = 4,
    N7110_KEYPAD_FIVE = 5,
    N7110_KEYPAD_SIX = 6,
    N7110_KEYPAD_SEVEN = 7,
    N7110_KEYPAD_EIGHT = 8,
    N7110_KEYPAD_NINE = 9,
    N7110_KEYPAD_STAR = 10,
    N7110_KEYPAD_HASH = 11,
    N7110_KEYPAD_MENU_LEFT = 100,
    N7110_KEYPAD_MENU_RIGHT = 101,
    N7110_KEYPAD_MENU_UP = 102,
    N7110_KEYPAD_MENU_DOWN = 103,
    N7110_KEYPAD_CALL_ACCEPT = 104,
    N7110_KEYPAD_CALL_REJECT = 105
} N7110_KEYPAD_KEYS;

enum {
    N7110_IME_T9 = 0,
    N7110_IME_T9_CAPS = 1,
    N7110_IME_ABC = 2,
    N7110_IME_ABC_CAPS = 3
} N7110_IME_METHODS;

#define N7110_KEYPAD_ZERO_ABC_CHARS  " 0"
#define N7110_KEYPAD_ONE_ABC_CHARS   ".,'?!\"1-()@/:"
#define N7110_KEYPAD_TWO_ABC_CHARS   "abc2"
#define N7110_KEYPAD_THREE_ABC_CHARS "def3"
#define N7110_KEYPAD_FOUR_ABC_CHARS  "ghi4"
#define N7110_KEYPAD_FIVE_ABC_CHARS  "jkl5"
#define N7110_KEYPAD_SIX_ABC_CHARS   "mno6"
#define N7110_KEYPAD_SEVEN_ABC_CHARS "pqrs7"
#define N7110_KEYPAD_EIGHT_ABC_CHARS "tuv8"
#define N7110_KEYPAD_NINE_ABC_CHARS  "wxyz9"
#define N7110_KEYPAD_STAR_ABC_CHARS  "@/:_;+&%*[]{}"
#define N7110_KEYPAD_HASH_CHARS N7110_IME_METHODS
#endif // N7110

sms1.csv:

945918410125,100
945918410538,100
945918411013,100
945918411409,100
945918413098,11
945918413451,11
945918415694,7
945918416040,7
945918416395,7
945918418836,8
945918419056,8
945918419697,3
945918420350,6
945918420570,6
945918420815,6
945918421541,5
945918421749,5
945918421987,5
945918422896,3
945918423125,3
945918423491,3
945918424860,0
945918425759,9
[...]

sms1.txt:

date: 1999-11-23 03:01:10
to: 00611015550117
text: rudolf where are you brrr
aotw{l3ts_dr1nk_s0m3_ehmg}

It’s pretty clear from keys.h that the csv contains the pair time, button pressed.

I had a bit of trouble with the timing and the 4th file was corrupted in some points (I guess), but I solved the challenge with the following script.

Exploit

#!/usr/bin/env python3

import pandas as pd
import sys

t9 = {
        '0': ' 0______________________________________________',
        '1': '.,\'?!\"1-()@/:_________________________________',
        '2': 'abc2____________________________________________',
        '3': 'def3____________________________________________',
        '4': 'ghi4____________________________________________',
        '5': 'jkl5____________________________________________',
        '6': 'mno6____________________________________________',
        '7': 'pqrs7___________________________________________',
        '8': 'tuv8____________________________________________',
        '9': 'wxyz9___________________________________________',
        '10': '@/:_;+&%*[]{}__________________________________',
        '11': ';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;',
        '100': '::::::::::::::::::::::::::::::::::::::::::::::',
        '101': '----------------------------------------------',
        '102': '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', 
        '103': '??????????????????????????????????????????????',
        '104': '//////////////////////////////////////////////',
        '105': '++++++++++++++++++++++++++++++++++++++++++++++',
    }

def main():
    # max_time = int(sys.argv[1])
    max_time = 1000
    csv_file = './sms4.csv'
    data = pd.read_csv(csv_file)
    times = list(data['TIME'])
    keys = list(data['KEY'])
    csv = [(x, y) for x, y in zip(times, keys)]
    flag = ''
    current_char = ''
    index = 0
    for i in range(len(csv) - 1):
        if (csv[i + 1][0] - csv[i][0]) < max_time and \
                csv[i][1] == csv[i + 1][1]:
            index += 1
        else:
            current_char = t9.get(str(csv[i][1]), '_')[index]
            index = 0
            flag += current_char
    print(flag)

if __name__ == '__main__':
    main()

Output:

:;alright pal hers!e? ye flag go_d lucj enter!?-k?ing it with those hooves lol its aotw{l3ts_dr1nk_s0m3_egg_og-0g_y0u_cr4zy_d33r}:0m.. .l ,p

The flag was corrupted, but after some minutes of guessing I eventually got it.

Flag

aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}

Mooo

Information

  • category : web
  • points : 98

Description

‘Moo may represent an idea, but only the cow knows.’ - Mason Cooley

Service: http://3.93.128.89:1204

Writeup

This is the initial web page.

It’s easy to guess that the server is using cowsay to print the cow. I tried basic shell injections, however they didn’t work.

One interesting “cow” is the custom one.

As we can see, the server interpretes the $tongue and $eyes from the respective text box. To do that the server pass to cowsay the -f flag which reads a custom perl file.

Example:

$e = chop($eyes);

$the_cow=<<EOC
	$e
EOC

What if we inject the EOC in the form?

And we got the flag :D.

Flag

AOTW{th3_p3rl_c0w_s4ys_M0oO0o0O}

Sudo Sudoku

Information

  • category : misc
  • points : 85

Description

Santa’s little helpers are notoriously good at solving Sudoku puzzles, so they made a special variant…

1 file

Writeup

Santa's little helpers are notoriously good at solving Sudoku puzzles.
Because regular Sudoku puzzles are too trivial, they have invented a variant.

    1 2 3   4 5 6   7 8 9
  +-------+-------+-------+
A | . . . | . . . | . . 1 |
B | . 1 2 | . . . | . . . |
C | . . . | . . . | 2 . . |
  +-------+-------+-------+
D | . . . | . . . | . . 2 |
E | . 2 . | . . . | . . . |
F | . . . | . . . | . . . |
  +-------+-------+-------+
G | . . . | . . . | 1 2 . |
H | 1 . . | . . 2 | . . . |
I | . . . | 1 . . | . . . |
  +-------+-------+-------+

In addition to the standard Sudoku puzzle above,
the following equations must also hold:

B9 + B8 + C1 + H4 + H4 = 23
A5 + D7 + I5 + G8 + B3 + A5 = 19
I2 + I3 + F2 + E9 = 15
I7 + H8 + C2 + D9 = 26
I6 + A5 + I3 + B8 + C3 = 20
I7 + D9 + B6 + A8 + A3 + C4 = 27
C7 + H9 + I7 + B2 + H8 + G3 = 31
D3 + I8 + A4 + I6 = 27
F5 + B8 + F8 + I7 + F1 = 33
A2 + A8 + D7 + E4 = 21
C1 + I4 + C2 + I1 + A4 = 20
F8 + C1 + F6 + D3 + B6 = 25

If you then read the numbers clockwise starting from A1 to A9, to I9, to I1 and
back to A1, you end up with a number with 32 digits.  Enclose that in AOTW{...}
to get the flag.

We have a sudoku that can’t be solved because we have too few numbers, however we have various constraints that can help us solve it.

I was tempted to do my sudoku solver, but I found out a very nice sudoku solver in z3 (python).

Now we need to add our constraints.

Exploit

#!/usr/bin/env python3

def cross(A, B):
    return [(a + b) for a in A for b in B]

class Sudoku:
    @staticmethod
    def parse_grid(puzzle):
        """
        A1 A2 A3 | A4 A5 A6 | A7 A8 A9
        B1 B2 B3 | B4 B5 B6 | B7 B8 B9
        C1 C2 C3 | C4 C5 C6 | C7 C8 C9
        –––––––––+––––––––––+–––––––––
        D2 D2 D3 | D4 D5 D6 | D7 D8 D9
        E1 E2 E3 | E4 E5 E6 | E7 E8 E9
        F1 F2 F3 | F4 F5 F6 | F7 F8 F9
        –––––––––+––––––––––+–––––––––
        G1 G2 G3 | G4 G5 G6 | G7 G8 G9
        H1 H2 H3 | H4 H5 H6 | H7 H8 H9
        I1 I2 I3 | I4 I5 I6 | I7 I8 I9

        puzzle = 'A1A2A3A4...' and every element holds a value of '123456789.'
        where the dot represents an empty cell.
        """

        s = Sudoku()

        if any(c not in "123456789." for c in puzzle) or len(puzzle) != 81:
            raise Exception("got invalid puzzle format")

        elements = cross("ABCDEFGHI", "123456789")
        s.values = {e: v for e,v in zip(elements, puzzle)}
        return s

    def __init__(self, values=dict()):
        # mapping cells -> "123456789." where the dot represents an empty cell
        # cells = cross product of "ABCDEFGHI" and "123456789"
        self.values = values

        # we define some additional informations that may be used by a solving 
        # function:
        rows, cols = "ABCDEFGHI", "123456789"
        self.elements = cross(rows, cols)

        self.unitlist = []
        self.unitlist += [cross(rows, c) for c in cols]
        self.unitlist += [cross(r, cols) for r in rows]
        self.unitlist += [cross(rs, cs) for rs in ["ABC", "DEF", "GHI"] \
                for cs in ["123", "456", "789"]]

        self.units = {e: [u for u in self.unitlist if e in u] \
                for e in self.elements}

    def is_solved(self):
        # assure that every cell holds a single value between 1 and 9:
        if not all(k in "123456789" for k in self.values.values()):
            return False

        # assure that every cell of every unit is unique in the proper unit:
        unitsolved = lambda u: set([self.values[e] for e in u]) \
                == set("123456789")
        return all(unitsolved(u) for u in self.unitlist)

    def __str__(self):
        lines, elements = [], cross("ABCDEFGHI", "123456789")

        print("[+] Puzzle:", ''.join(self.values[e] for e in elements))

        for index_row, row in enumerate("ABCDEFGHI"):
            if index_row % 3 == 0:
                lines.append("+–––––––––+–––––––––+–––––––––+")

            line = ''
            for index_col, col in enumerate("123456789"):
                line += "{1} {0} ".format(self.values[row + col], '|' \
                        if index_col % 3 == 0 else '')
            lines.append(line + '|')

        lines.append("+–––––––––+–––––––––+–––––––––+")
        return '\n'.join(lines) + '\n'

def Z3Solving(sudoku):
    from z3 import Solver, Int, Or, Distinct, sat

    elements = cross("ABCDEFGHI", "123456789")
    symbols = {e: Int(e) for e in elements}
    # first we build a solver with the general constraints for sudoku puzzles:
    s = Solver()

    # assure that every cell holds a value of [1,9]
    for symbol in symbols.values():
        s.add(Or([symbol == i for i in range(1, 10)]))

    # assure that every row covers every value:
    for row in "ABCDEFGHI":
        s.add(Distinct([symbols[row + col] for col in "123456789"]))

    # assure that every column covers every value:
    for col in "123456789":
        s.add(Distinct([symbols[row + col] for row in "ABCDEFGHI"]))
    
    s.add(symbols["B9"] + symbols["B8"] + symbols["C1"] + symbols["H4"] + symbols["H4"] == 23)
    s.add(symbols["A5"] + symbols["D7"] + symbols["I5"] + symbols["G8"] + symbols["B3"] + symbols["A5"] == 19)
    s.add(symbols["I2"] + symbols["I3"] + symbols["F2"] + symbols["E9"] == 15)
    s.add(symbols["I7"] + symbols["H8"] + symbols["C2"] + symbols["D9"] == 26)
    s.add(symbols["I6"] + symbols["A5"] + symbols["I3"] + symbols["B8"] + symbols["C3"] == 20)
    s.add(symbols["I7"] + symbols["D9"] + symbols["B6"] + symbols["A8"] + symbols["A3"] + symbols["C4"] == 27)
    s.add(symbols["C7"] + symbols["H9"] + symbols["I7"] + symbols["B2"] + symbols["H8"] + symbols["G3"] == 31)
    s.add(symbols["D3"] + symbols["I8"] + symbols["A4"] + symbols["I6"] == 27)
    s.add(symbols["F5"] + symbols["B8"] + symbols["F8"] + symbols["I7"] + symbols["F1"] == 33)
    s.add(symbols["A2"] + symbols["A8"] + symbols["D7"] + symbols["E4"] == 21)
    s.add(symbols["C1"] + symbols["I4"] + symbols["C2"] + symbols["I1"] + symbols["A4"] == 20)
    s.add(symbols["F8"] + symbols["C1"] + symbols["F6"] + symbols["D3"] + symbols["B6"] == 25)


    # assure that every block covers every value:
    for i in range(3):
        for j in range(3):
            s.add(Distinct([symbols["ABCDEFGHI"[m + i * 3] + \
                    "123456789"[n + j * 3]] for m in range(3) for n in range(3)]))
    
    # now we put the assumptions of the given puzzle into the solver:
    for elem, value in sudoku.values.items():
        if value in "123456789":
            s.add(symbols[elem] == value)

    if not s.check() == sat:
        raise Exception("unsolvable")

    model = s.model()
    values = {e: model.evaluate(s).as_string() for e, s in symbols.items()}
    return Sudoku(values)

def main(puzzle):
    print("[+] processing puzzle:", puzzle)

    s = Sudoku.parse_grid(puzzle)
    print(s)

    print("[+] trying to solve it with z3")
    s_solved = Z3Solving(s)
    print("[+] it is solved:", s_solved.is_solved())
    print(s_solved)

if __name__ == "__main__":
    from sys import argv

    if len(argv) != 2:
        main("........1.12............2..........2.2......................12.1....2......1.....")
    else:
        main(argv[1])

Output:

 > ./exploit.py 
[+] processing puzzle: ........1.12............2..........2.2......................12.1....2......1.....
[+] Puzzle: ........1.12............2..........2.2......................12.1....2......1.....
+–––––––––+–––––––––+–––––––––+
| .  .  . | .  .  . | .  .  1 |
| .  1  2 | .  .  . | .  .  . |
| .  .  . | .  .  . | 2  .  . |
+–––––––––+–––––––––+–––––––––+
| .  .  . | .  .  . | .  .  2 |
| .  2  . | .  .  . | .  .  . |
| .  .  . | .  .  . | .  .  . |
+–––––––––+–––––––––+–––––––––+
| .  .  . | .  .  . | 1  2  . |
| 1  .  . | .  .  2 | .  .  . |
| .  .  . | 1  .  . | .  .  . |
+–––––––––+–––––––––+–––––––––+

[+] trying to solve it with z3
[+] it is solved: True
[+] Puzzle: 864729531912453768375618249649875312721936854538241697486597123197362485253184976
+–––––––––+–––––––––+–––––––––+
| 8  6  4 | 7  2  9 | 5  3  1 |
| 9  1  2 | 4  5  3 | 7  6  8 |
| 3  7  5 | 6  1  8 | 2  4  9 |
+–––––––––+–––––––––+–––––––––+
| 6  4  9 | 8  7  5 | 3  1  2 |
| 7  2  1 | 9  3  6 | 8  5  4 |
| 5  3  8 | 2  4  1 | 6  9  7 |
+–––––––––+–––––––––+–––––––––+
| 4  8  6 | 5  9  7 | 1  2  3 |
| 1  9  7 | 3  6  2 | 4  8  5 |
| 2  5  3 | 1  8  4 | 9  7  6 |
+–––––––––+–––––––––+–––––––––+

Flag

AOTW{86472953189247356794813521457639}

Tiny Runes Game

Information

  • category: reversing, misc
  • points : 137

Description

One of Santa’s Little Helpers received an unusual Christmas wish, a copy of the yet to be released Deus Hex game. All they managed to find were fragments from the dialogue system. Can you decode the last one?

4 bin files, 3 txt files, 3 png files.

Writeup

The txt files are the decoded binary files.

xxd lines1.bin:

00000000: 5469 4e79 4d65 5461 0000 0010 0000 0002  TiNyMeTa........
00000010: 0008 0008 080c 0048 0002 002a 5478 5472  .......H...*TxTr
00000020: 0000 0301 8950 4e47 0d0a 1a0a 0000 000d  .....PNG........
00000030: 4948 4452 0000 0040 0000 0060 0103 0000  IHDR...@...`....
00000040: 0097 0be6 ab00 0000 0650 4c54 4500 0000  .........PLTE...
00000050: f0d1 c563 8e08 7e00 0002 b649 4441 5478  ...c..~....IDATx
00000060: da25 5241 641c 6114 feb6 7da6 1123 62c5  .%RAd.a...}..#b.
00000070: fa0f 2b5d 6bc5 1a3d 8c88 556b b322 a71c  ..+]k..=..Uk."..
00000080: 6a45 2951 ca94 1a55 113d 5444 ac3d 8c3d  jE)Q...U.=TD.=.=
00000090: ac94 1e6a 4f23 6a34 ac68 ab6a 0e51 5511  ...jO#j4.h.j.QU.
000000a0: e918 6be5 9043 8d55 7bd8 ae3d fce6 107b  ..k..C.U{..=...{
000000b0: 5891 8e26 e99b f430 e679 effd df7b effb  X..&...0.y...{..
000000c0: 3e2a 01f5 96fb 7191 34c0 51e6 f640 709e  >*....q.4.Q..@p.
000000d0: 770a d980 0365 428a c31e 07ea 682c 3b49  w....eB.....h,;I
000000e0: 9022 1161 7357 5047 0497 d805 a803 091b  .".asWPG........
000000f0: d0a9 5a37 be1c 2c25 750a 81bc 022e f1b7  ..Z7..,%u.......
00000100: a002 0aa5 ae9f 3e16 e8bf a211 50ec 022a  ......>.....P..*
00000110: 4509 0363 6362 9d7b 6611 0588 c886 8522  E..ccb.{f......"
00000120: 6013 d69b b72f 91ce c4cf 3998 d1a8 fcb6  `..../....9.....
00000130: a9f8 ee55 48d9 0da8 f30b fa14 f58e 9ba2  ...UH...........
00000140: 5b73 dbd4 d980 0e4b 5768 7304 83d1 ef51  [s.....KWhs....Q
00000150: 1dd7 09e0 1454 6b24 fcaf ef86 a006 9019  .....Tk$........
00000160: 6438 c0fd e369 e8b7 caa4 e497 647a d50e  d8...i......dz..
00000170: 4985 b53a 9ce7 e93c eb02 53f3 5942 37de  I..:...<..S.YB7.
00000180: a89d 2529 e375 1487 aabb a8b8 99d7 8f28  ..%).u.........(
00000190: 9402 e385 bd1c e3f4 119d 9046 a9fe 1d44  ...........F...D
000001a0: e5f7 1a0d f93d 4c5f 30ce ef00 234d 3275  .....=L_0...#M2u
000001b0: e8e1 c393 3d72 c004 edec f0f4 d2b2 55f2  ....=r........U.
000001c0: 5023 6836 34cd 6d50 7527 0904 cc4f 98fa  P#h64.mPu'...O..
000001d0: 7b17 3f12 fc3c e4cc 2238 30dc 3fd0 7a3a  {.?..<.."80.?.z:
000001e0: 2130 edc0 13b3 cc8f 273c 088b 71bc 6747  !0......'<..q.gG
000001f0: 9eaa 328e 176a 9ee6 916f 5743 9422 ce4c  ..2..j...oWC.".L
00000200: 85a9 a31c eb35 7d81 508b 3a0a b590 6239  .....5}.P.:...b9
00000210: 8c16 8dba 51fa 2816 a589 6c9d ff5b 54b3  ....Q.(...l..[T.
00000220: 1c18 8ed9 a001 84c4 8d5e e55f a5b4 0993  .........^._....
00000230: d2ca 7125 846d d250 f95f 635a c440 e922  ..q%.m.P._cZ.@."
00000240: 20b4 93bd 2507 3d72 aa75 e995 3cbe b4ce   ...%.=r.u..<...
00000250: 3d3d ff80 fc87 3223 9167 9300 7d60 5ad0  ==....2#.g..}`Z.
00000260: 0373 3b03 7427 48d8 db8c 3212 5470 3331  .s;.t'H...2.Tp31
00000270: 1f03 326c 615a 02df 8949 b620 8b20 e373  ..2laZ...I. . .s
00000280: 4efa c5ad 0bc2 a4e6 01e3 4f94 46cb 07fb  N.........O.F...
00000290: 8dc2 b5fd d60b e09c b02f 4ef2 c024 f3ac  ........./N..$..
000002a0: ab23 a1f0 62e7 b32b 5da8 6bb1 7f2c a001  .#..b..+].k..,..
000002b0: d20b 6e6e d94a 0f63 4d99 e999 2155 62d3  ..nn.J.cM...!Ub.
000002c0: d606 0572 0b9c 7bf3 328c 4bec c2f0 c6a2  ...r..{.2.K.....
000002d0: 7c22 1f78 16b0 1295 c35b 3418 30cd 8c44  |".x.....[4.0..D
000002e0: 42ae 38b5 6f3f 9955 76d3 a0af c60a f64a  B.8.o?.Uv......J
000002f0: 69ee 66e6 4f3b 9da4 ee70 499d 6bc7 6603  i.f.O;...pI.k.f.
00000300: 443e ef4a 0e02 bd7b a59c 81fd e3dd 4cf9  D>.J...{......L.
00000310: 0707 9205 d0b7 2b6c e400 0000 0049 454e  ......+l.....IEN
00000320: 44ae 4260 824c 694e 6500 0000 3c05 0103  D.B`.LiNe...<...
00000330: 0a04 0806 0903 0700 0500 0a01 0b00 0502  ................
00000340: 0704 0806 0700 0403 0b07 0a05 0b05 0905  ................
00000350: 0905 0904 0802 0505 0905 0905 0904 0802  ................
00000360: 0505 0905 0905 0906 074c 694e 6500 0000  .........LiNe...
00000370: 5405 0103 0a04 0806 0903 0700 0500 0a01  T...............
00000380: 0b00 0502 0704 0806 0702 0504 0800 0a00  ................
00000390: 0901 0b07 0a01 0700 0900 0a04 0805 0701  ................
000003a0: 0b07 0a04 0800 0603 0707 0303 0704 0803  ................
000003b0: 0b04 0801 0604 0300 0404 0801 0707 0a00  ................
000003c0: 0505 0906 07                             .....

lines1.txt:

JC Denton: "Paul... I... I..."
JC Denton: "I thought you were a GEP gun."

As we can see the binary is a png file, in fact if we remove the first bytes we obtain a png.

If we try to extract all the png from all the binary files we get the same image.

The only thing that change in the png files are the bytes after the IEND chunk which marks the end of the png. Strangely these bytes are all low bytes.

At first I thought about a substitution cipher or something like that. I did various try but I didn’t find anything.

Another idea that I had was to xor those bytes with the one in the payload of the PNG, however they differ in lengths and so it didn’t worked and it was frustrating to recompute the checksums.

After a bit help I understood that the solution was to write the “matrix” in the extracted image and that those low bytes after IEND were the pair of row, column in the matrix.

Let’s see how it works.

Firstly we extract the bytes from the 4th binary file.

$ xxd -p lines4.bin
54694e794d655461000000100000000200080008080c0052000200345478
54720000030189504e470d0a1a0a0000000d494844520000004000000060
[...]
# These 
00003c0501010b0506070102070408060704050009040804020507040801
06010b0103050804080501030a05080408070404080602050b030b010705
0806074c694e65000000680501030a0408060903070005000a010b000502
0704080607070404080602050b030b01070508040807040405040a040901
09000600090503000a02020503020207030200000a000a01050005020206

Then we create an exploit to solve the challenge.

Exploit

#!/usr/bin/env python

chars = [
    ["Q", "?", "0", "\\", "H", "$", "Y", ","],
    ["R", "-", "L", "^", "K", "J", "┘", "k"],
    ["s", "#", "_", "/", "m", "=", "f", "9"],
    ["7", "d", "-", "N", "E", "4", "q", "r"],
    ["P", "i", "└", "V", "`", "&", "X", "A"],
    ["n", "3", "I", "┌", "O", "*", ";", "Z"],
    ["w", "G", "p", "B", "8", "c", "S", "j"],
    ["F", "g", ":", "e", "b", "y", "\"", "v"],
    ["%", "+", "┐", "1", " ", "!", "M", "@"],
    ["h", "{", "2", "x", "W", ".", "D", "}"],
    ["t", "U", "|", "C", "T", "z", "6", "u"],
    ["I", "o", ">", "a", "5", "l", "<", "'"]
]
hex = ("0501010b050607010207040806070405000904080402050704080106010b0103050804080501030a05080408070404080602050b030b0107050806074c694e65000000680501030a0408060903070005000a010b0005020704080607070404080602050b030b01070508040807040405040a04090109000600090503000a02020503020207030200000a000a0105000502020602010403080105020206020200070304020503000a07090607")
hex = hex.replace("4c694e65","ffffffff")

payload = bytes.fromhex(hex)
blocks = [payload[i:i + 2] for i in range(0, len(payload), 2)]
flag = ''
for block in blocks:
    if block == b'\xff\xff':
        flag += '\n'
    else:
        try:
            flag += chars[block[1]][block[0]]
        except:
            continue
print(flag)

Output:

Jock: "Oh my God! JC! A flag!"

QJC Denton: "A flag! AOTW{wh4t_4_r0tt3n_fi13_f0rm4t}"

Flag

AOTW{wh4t_4_r0tt3n_fi13_f0rm4t}