UP | HOME

Shattered Tablet

Table of Contents

Introduction

I had no intention of writing today, I just logged to get some practice in, on the basics, since I was tired to go into much theory, but this one was solved in less than 15-20 minutes. I thought why not: Let’s try to get some writing practice as well.

Deep in an ancient tomb, you’ve discovered a stone tablet with secret information on the locations of other relics. However, while dodging a poison dart, it slipped from your hands and shattered into hundreds of pieces. Can you reassemble it and read the clues?

Static analysis:

We are given just a single binary tablet, and the flag lies inside it, since there is no remote machine to launch.

Decompiling

Immediately upon seeing the decompiled main, I thought of a similar challenge I recently completed: It must have been spooky license but I am not 100% sure of it.

if (((((uStack_28._2_1_ == '4') && (uStack_38._4_1_ == '3')) && (uStack_28._4_1_ == 'r')) &&
    ((((uStack_48._1_1_ == 'T' && (uStack_38._5_1_ == 'v')) &&
      ((uStack_48._6_1_ == '0' && ((uStack_28._7_1_ == '}' && (uStack_28._6_1_ == 'd')))))) &&
     (uStack_30._7_1_ == 'r')))) &&
   ((((((uStack_30._5_1_ == '3' && (uStack_40 == '3')) && (uStack_38._6_1_ == 'e')) &&
      ((uStack_28._3_1_ == '1' && (uStack_48._5_1_ == 'r')))) &&
     ((uStack_48 == 'H' && ((uStack_28 == '3' && (uStack_38._2_1_ == '.')))))) &&
    (((((uStack_40._5_1_ == '4' &&
        (((((uStack_48._3_1_ == '{' && (uStack_40._2_1_ == '_')) && (uStack_38 == '.')) &&
          ((uStack_48._4_1_ == 'b' && (uStack_48._7_1_ == 'k')))) && (uStack_40._7_1_ == 't')))) &&
       (((uStack_40._6_1_ == 'r' && (uStack_38._3_1_ == 'n')) &&
        ((uStack_30._1_1_ == 't' &&
         (((uStack_38._1_1_ == '.' && (uStack_40._1_1_ == 'n')) && (uStack_30._6_1_ == '_')))))))) &&
      (((uStack_30._2_1_ == '0' && (uStack_30 == '_')) && (uStack_40._4_1_ == 'p')))) &&
     ((((uStack_38._7_1_ == 'r' && (uStack_30._4_1_ == 'b')) &&
       ((uStack_28._1_1_ == 'p' &&
        (((uStack_48._2_1_ == 'B' && (uStack_30._3_1_ == '_')) && (uStack_40._3_1_ == '4')))))) &&
      (uStack_28._5_1_ == '3')))))))) {

We are given a set of constraints we need the flag to meet. To do that we can use the z3-solver library, though it feels even easier than that. In fact I can do it by myself probably:

  1. Write the checks above in a separate sample file
  2. Break it down to its essentials (see shell command)
  3. Some vim quick formatting tricks and … We’re good to go:

cat test.txt | sed -E 's/&&|\)+|\(+/\n/g' | grep uStack | uniq | sort
Ustack_48: HTB{br0k
Ustack_40: 3n_4p4rt
Ustack_38: ...n3ver
Ustack_30: _t0_b3_r
Ustack_28: 3p41r3d}

Update: z3 solution

I wanted to test my z3 skills, since it took me a long time in the previous challenge. This one was more tedious, but here is the source:

import z3

# To find the length I simply piped the shell command to wc
letters = [z3.Int(f's_{i}') for i in range(40) ]

solver = z3.Solver()

# Actually unnecessary, since we have the letters in == 
# for l in letters:
#     solver.add(z3.And( l >= 33, l <= 127))

solver.add(letters[33] == ord('p')) 
solver.add(letters[34] == ord('4'))
solver.add(letters[32] == ord('3')) 
solver.add(letters[35] == ord('1')) 
solver.add(letters[36] == ord('r'))
solver.add(letters[37] == ord('3'))
solver.add(letters[38] == ord('d'))
solver.add(letters[39] == ord('}')) 
solver.add(letters[24] == ord('_'))
solver.add(letters[25] == ord('t'))
solver.add(letters[26] == ord('0'))
solver.add(letters[27] == ord('_'))
solver.add(letters[28] == ord('b'))
solver.add(letters[29] == ord('3'))
solver.add(letters[30] == ord('_'))
solver.add(letters[31] == ord('r'))
solver.add(letters[16] == ord('.'))
solver.add(letters[17] == ord('.'))
solver.add(letters[18] == ord('.'))
solver.add(letters[19] == ord('n'))
solver.add(letters[20] == ord('3'))
solver.add(letters[21] == ord('v'))
solver.add(letters[22] == ord('e'))
solver.add(letters[23] == ord('r'))
solver.add(letters[9]  == ord('n'))
solver.add(letters[10] == ord('_'))
solver.add(letters[8]  == ord('3'))
solver.add(letters[11] == ord('4'))
solver.add(letters[12] == ord('p'))
solver.add(letters[13] == ord('4')) 
solver.add(letters[14] == ord('r')) 
solver.add(letters[15] == ord('t'))
solver.add(letters[1]  == ord('T')) 
solver.add(letters[2]  == ord('B')) 
solver.add(letters[3]  == ord('{')) 
solver.add(letters[4]  == ord('b')) 
solver.add(letters[5]  == ord('r'))
solver.add(letters[6]  == ord('0')) 
solver.add(letters[7]  == ord('k'))
solver.add(letters[0]  == ord('H')) 

if solver.check() == z3.sat:
     m = solver.model()
     answ = ''
     for i in letters:
         # as_string does not work -> the integer is transformed into a string.
         answ += (f'{chr(m.evaluate(i).as_long())}')
     print(answ)

Originally created on 2025-02-15 Sat 00:00