LA CTF 2024 writeup&upsolve

２回目、自分用に整理のため。
毎週取り組みたいです。。。との思い

# LA CTF 2024
今回もソロ参加、webを見つつ他の問題もいろいろと。
upsolveで解けた問題も多かった回でした。

チームで参加したいですね...誘ってください ; ;

<!--

-->


## terms-and-conditions [web 771 solves]
問題文
```
Welcome to LA CTF 2024! All you have to do is accept the terms and conditions and you get a flag!
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670010/97949cc0-07b3-ad02-eb2e-d0f6ee631d10.png)

大会のルールが書いてありますね、ご丁寧
真ん中の `I accept` をクリックしたらflagが手に入りそうです。
ただ、マウスを近づくと `I accept` のボタンがマウスから逃げるようです。

ソースコードにスクリプトの詳細あり

開発ツールを開いたときにコンソールを利用できないとアラートがでますが、再読み込みでなんとかなりそうですね。
```js
// 省略

const accept = document.getElementById("accept");
document.body.addEventListener("touchstart", (e) => {
    document.body.innerHTML = "<div><h1>NO TOUCHING ALLOWED</h1></div>";
});
let tx = 0;
let ty = 0;
let mx = 0;
let my = 0;
window.addEventListener("mousemove", function (e) {
    mx = e.clientX;
    my = e.clientY;
});
setInterval(function () {
    const rect = accept.getBoundingClientRect();
    const cx = rect.x + rect.width / 2;
    const cy = rect.y + rect.height / 2;
    const dx = mx - cx;
    const dy = my - cy;
    const d = Math.hypot(dx, dy);
    const mind = Math.max(rect.width, rect.height) + 10;
    const safe = Math.max(rect.width, rect.height) + 25;
    if (d < mind) {
        const diff = mind - d;
        if (d == 0) {
            tx -= diff;
        } else {
            tx -= (dx / d) * diff;
            ty -= (dy / d) * diff;
        }
    } else if (d > safe) {
        const v = 2;
        const offset = Math.hypot(tx, ty);
        const factor = Math.min(v / offset, 1);
        if (offset > 0) {
            tx -= tx * factor;
            ty -= ty * factor;
        }
    }
    accept.style.transform = `translate(${tx}px, ${ty}px)`;
}, 1);
let width = window.innerWidth;
let height = window.innerHeight;
setInterval(function () {
    if (window.innerHeight !== height || window.innerWidth !== width) {
        document.body.innerHTML = "<div><h1>NO CONSOLE ALLOWED</h1></div>";
        height = window.innerHeight;
        width = window.innerWidth;
    }
}, 10);
```
window上のマウスの動きに応じて、ボタンが移動するようですね。
タッチパネルでは？とも思いましたが対策されてました。。。

なのであらかじめボタンがあるところにマウスを移動させ、F5で再読み込みをすれば、マウスカーソル下にボタンが表示されクリックできますね。

```:実行結果
flag:
lactf{that_button_was_definitely_not_one_of_the_terms}
```
ほかにもコンソール上で、ボタンの要素である`accept`のIDを`getElementByID`で取り出して実行したらできそう。

## flaglang [web 607 solves]
問題文
```
Do you speak the language of the flags?
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670010/a7438054-4fab-506f-2844-60298fba63a8.png)

加えて、ソースコードがいろいろ渡されていますね。
詳しくは`app.js`にいろいろ書いてありますね。

flagは`Flagistan`という架空の国の挨拶らしいです。すごいですね
ただ、国を変えて挨拶を見ようとしてもエラーがでます。
userISOというcookieに含まれている情報をもとに制限しているようです。そのため、cookieですべて削除したらいけそうです。開発ツールからcookieを削除して、国を変えてみます。

そしたら、flagが表示されました。

```:実行結果
flag:
lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}
```

## shattered-memories [rev 697 solves]
問題文
```
Do you speak the language of the flags?
```

問題形式がrevでファイルが渡されたので、とりあえず`ghidra`にぶちこんでみます。
逆アセンブルした後に、main関数を見てみるとflagについていろいろ書いてありました。

```c
undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  undefined8 uVar3;
  char local_98 [8];
  char acStack_90 [8];
  char acStack_88 [8];
  char acStack_80 [8];
  char acStack_78 [108];
  int local_c;
  
  puts("What was the flag again?");
  fgets(local_98,0x80,stdin);
  strip_newline(local_98);
  sVar2 = strlen(local_98);
  if (sVar2 == 0x28) {
    local_c = 0;
    iVar1 = strncmp(acStack_90,"t_what_f",8);
    local_c = local_c + (uint)(iVar1 == 0);
    iVar1 = strncmp(acStack_78,"t_means}",8);
    local_c = local_c + (uint)(iVar1 == 0);
    iVar1 = strncmp(acStack_80,"nd_forge",8);
    local_c = local_c + (uint)(iVar1 == 0);
    iVar1 = strncmp(local_98,"lactf{no",8);
    local_c = local_c + (uint)(iVar1 == 0);
    iVar1 = strncmp(acStack_88,"orgive_a",8);
    local_c = local_c + (uint)(iVar1 == 0);
    switch(local_c) {
    case 0:
      puts("No, that definitely isn\'t it.");
      uVar3 = 1;
      break;
    case 1:
      puts("I\'m pretty sure that isn\'t it.");
      uVar3 = 1;
      break;
    case 2:
      puts("I don\'t think that\'s it...");
      uVar3 = 1;
      break;
    case 3:
      puts("I think it\'s something like that but not quite...");
      uVar3 = 1;
      break;
    case 4:
      puts("There\'s something so slightly off but I can\'t quite put my finger on it...");
      uVar3 = 1;
      break;
    case 5:
      puts("Yes! That\'s it! That\'s the flag! I remember now!");
      uVar3 = 0;
      break;
    default:
      uVar3 = 0;
    }
  }
  else {
    puts("No, I definitely remember it being a different length...");
    uVar3 = 1;
  }
  return uVar3;
}
```

何かしらの条件を達成したらflagが出力されそうですが、わかりやすいようにflagがバラバラになっているのでいい感じに組み合わせたら解けそうです。

(これだけならファイルをstringsコマンドで出力して調べられそうとも思いましたが)

いい感じに組み合わせると
```
flag:
lactf{not_what_forgive_and_forget_means}
```

時間内で解けた問題はここまで。
残りはupsolveです、もっと時間を確保できれば、、、

## aplet321 [rev 455 solves]
問題文
```
Unlike Aplet123, Aplet321 might give you the flag if you beg him enough.
```
``
nc chall.lac.tf 31321
``

問題形式がrevでファイルが渡されたので、とりあえず`ghidra`にぶちこんでいろいろ(略)

main関数は以下
```c
undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  char *pcVar3;
  int iVar4;
  int iVar5;
  char local_238;
  char acStack_237 [519];
  
  setbuf(stdout,(char *)0x0);
  puts("hi, i\'m aplet321. how can i help?");
  fgets(&local_238,0x200,stdin);
  sVar2 = strlen(&local_238);
  if (5 < sVar2) {
    iVar4 = 0;
    iVar5 = 0;
    pcVar3 = &local_238;
    do {
      iVar1 = strncmp(pcVar3,"pretty",6);
      iVar5 = iVar5 + (uint)(iVar1 == 0);
      iVar1 = strncmp(pcVar3,"please",6);
      iVar4 = iVar4 + (uint)(iVar1 == 0);
      pcVar3 = pcVar3 + 1;
    } while (pcVar3 != acStack_237 + ((int)sVar2 - 6));
    if (iVar4 != 0) {
      pcVar3 = strstr(&local_238,"flag");
      if (pcVar3 == (char *)0x0) {
        puts("sorry, i didn\'t understand what you mean");
        return 0;
      }
      if ((iVar5 + iVar4 == 0x36) && (iVar5 - iVar4 == -0x18)) {
        puts("ok here\'s your flag");
        system("cat flag.txt");
        return 0;
      }
      puts("sorry, i\'m not allowed to do that");
      return 0;
    }
  }
  puts("so rude");
  return 0;
}
```
`iVar5 + iVar4 == 0x36` と `iVar5 - iVar4 == -0x18` を満たせば `cat flag.txt` でflagが見れそうですね。
`iVar5`は

```
# 0x36 : 54
# 0x18 : 24

# iVar5 = 54 - 39 = 15
# iVar4 = 78 / 2 = 39
```
条件のためには、入力に
・`pretty` × 15
・`please` × 39
・`flag`
が含まれている必要がある。

あとは適当に入力のためにプログラムを組み、それを実行すればよい

```:入力
flag pretty pretty pretty pretty pretty pretty pretty pretty　pretty pretty pretty pretty pretty pretty pretty please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please
```

```:実行結果
$ nc chall.lac.tf 31321
hi, i'm aplet321. how can i help?
flag pretty pretty pretty pretty pretty pretty pretty pretty　pretty pretty pretty pretty pretty pretty pretty please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please
ok here's your flag
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}
```

## valentines-day [crypto 452 solves]
問題文
```
Happy Valentine's Day! I'm unfortunately spending my Valentine's Day working on my CS131 homework. I'm getting bored so I wrote something for my professor. To keep it secret, I encrypted it with a Vigenere cipher with a really long key (161 characters long!)

As a hint, I gave you the first part of the message I encrypted. Surely, you still can't figure it out though?

Flag format is lactf{xxx} with only lower case letters, numbers, and underscores between the braces.
```
渡されたものは`ct.txt`と`intro.txt`の２つ。
ヴィジュネル暗号というものが使われているらしい。
調べてみると`Rot13`などの暗号化方法と似ていますね。 

違う点として、
・公開鍵暗号のような特定の文章（鍵）がある
というもの。すべて13文字ずらすとかではなく、一定の周期で文章（鍵）に対応して文字が順々にずれるということ。
ただし、句読点やアンダーバーといった記号は無視され、あくまで文字だけずらすことに注意。（ロスタイム ; ;）

今回は鍵が161文字という条件で、さらに**暗号文**と**元の文章**が用意されているため、鍵の特定が一部できる。
ただし、`intro.txt`で渡された元の文章が161文字に満たないため完全な鍵の特定はできなさそうだが。

とりあえず、鍵を特定するコードの作成

```python
m = "On this Valentine's day, I wanted to show my love for professor Paul Eggert. This challenge is dedicated to him. Enjoy the challenge!"

key = "Br olzy Jnyetbdrc'g xun, V avrkkr gb sssp km frja sbv kvflsffoi Jnuc Sathrg. Wkmk gytjzyakz mj jsqvcmtoh rc bkd. Canjc kns puadlctus!"
num = ord('Z') - ord('A') + 1

ans = ""

for i in range(len(key)):
    tmp1 = m[i]
    tmp2 = key[i]
    if ((ord('a') <= ord(tmp1) & ord(tmp1) <= ord('z')) or (ord('A') <= ord(tmp1) & ord(tmp1) <= ord('Z'))):
        tmp3 = ord('a') + ((ord(tmp2) - ord(tmp1) + num) % num)
        ans += chr(tmp3)

print(len(ans))
print(ans)
```

```:実行結果
106
nevergonnagiveyouupnevergonnaletyoudownnevergonnarunaroundanddesertyounevergonnamakeyoucrynevergonnasaygoo
```

得られた鍵は106文字である。なにかの文章のようだが...？

暗号文(ct.txt)の部分で明らかにflagの箇所とみられる部分がある。それに対して不完全な鍵を用いて解読を行ってみる。
処理が面倒なので、flag部分の記号はあらかじめ削除を行った。
出力されるものの中で、初めの5語がlactfのもので絞る。

```python
m = ""
ct = "ropgfqvjaldfuxaxzbkgbqjecihdtnrhdreexij"

key = "nevergonnagiveyouupnevergonnaletyoudownnevergonnarunaroundanddesertyounevergonnamakeyoucrynevergonnasaygoo"
num = ord('Z') - ord('A') + 1

print("flag:")
for i in range(len(key)):
    ans = ""
    for j in range(len(ct)):
        tmp1 = ct[j]
        tmp2 = key[(i + j) % len(key)]
        tmp3 = ord('a') + ((ord(tmp1) - ord(tmp2) + num) % num)
        ans += chr(tmp3)
    if ans[0:5] == 'lactf':
        print(ans)
```

```:実行結果
flag:
lactfknownplaintextandwereofftotheraces
lactffrqcxjcgbnkvggpvcwrcrnqtwdnqoeruff
lactfzbwauplhuakwygoxzqgoouzyjabpereliz
lactfevzwnplsgckvggpvcwrcqhfnzduzwanruw
lactfyvluxpsqcwgtnxtbkbjyktjzyedinnyjvw
```

一番上のものがflagのような形をしている。あとはあらかじめ削除した記号を復元したら以下のようになる。

```
flag:
lactf{known_plaintext_and_were_off_to_the_races}
```

## very-hot [crypto 416 solves]
問題文
```
I didn't think that using two primes for my RSA was sexy enough, so I used three.
```

よくあるRSA暗号の一つ多いバージョン。初めてz3を使ってみた。
z3については次のサイトを参考にした。

参考：[CTF/Toolkit/z3py - 電気通信大学MMA](https://wiki.mma.club.uec.ac.jp/CTF/Toolkit/z3py)

z3を用いてpの計算。
↓
それぞれの鍵を求める。
↓
powして求める。

```python
from z3 import *
from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long, inverse

p = Int("p")

s = Solver()

# n = pq
n = 10565111742779621369865244442986012561396692673454910362609046015925986143478477636135123823568238799221073736640238782018226118947815621060733362956285282617024125831451239252829020159808921127494956720795643829784184023834660903398677823590748068165468077222708643934113813031996923649853965683973247210221430589980477793099978524923475037870799
s.add(p * (p + 6) * (p + 12) == n)

s.check()

# 公開鍵
e = 65537

ct = 9953835612864168958493881125012168733523409382351354854632430461608351532481509658102591265243759698363517384998445400450605072899351246319609602750009384658165461577933077010367041079697256427873608015844538854795998933587082438951814536702595878846142644494615211280580559681850168231137824062612646010487818329823551577905707110039178482377985
p = 21942765653871439764422303472543530148312720769660663866142363370143863717044484440248869144329425486818687730842077

# mod
phi = (p-1) * (p+6-1) * (p+12-1)

# 暗号鍵
d = inverse(e, phi)

plain = pow(ct, d, n)

print(long_to_bytes(plain).decode())
```

```:実行結果
lactf{th4t_w45_n0t_so_53xY}
```