ヒデホヒのおもちゃばこ

オリスロとかつくったものとかてきとーに

Seccon Beginners CTF 2019 writeup

まえがき

ひきこもってくろうぃずやるつもりができなくなったのでさんかしたよ

どれぐらいがんばったか

1220Pt 84位でした。まあまあえらい

f:id:pekko1215:20190526204537p:plain
マネージャ

ときかた

[warmup] Ramen

「'」をsearchに投げるとエラーが出るので、SQLインジェクションができると予想
「1' UNION SELECT TABLE_NAME,TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES -- 」とか投げてデータベース内の情報を読み取ると、flagというテーブルがあることがわかる。 最後に「1' UNION SELECT flag,NULL FROM flag -- 」を投げるとフラグが出てくるよ。

katsudon

base64っぽい文字列があるのでdecodeする。おわり。

[warmup] Seccompare

objdump -D seccompare

するとmain関数にそれっぽいmovbがいっぱいあるので、そのまま文字に直していけばおわり。 f:id:pekko1215:20190526205401p:plain

Leakage

とりあえずobjdump f:id:pekko1215:20190526210250p:plain 4005ffで文字列の長さが34文字かチェックする。フラグの長さがわかる。
とりあえず適当にaを34文字入れて、プログラムの動きをgdbでみてみる。
convert関数の戻り値と、入力文字を比較している場所がある。(0x400643)
gdbで戻り値を確認する。 f:id:pekko1215:20190526211207p:plain f:id:pekko1215:20190526211252p:plain 0x63、つまり「c」が出てくる。

set $rax=0x63

でRAXをその都度合わせつつ、最後の実行までフラグを抜き取る。

So Tired

base64->deflate->base64->deflate...になっている。 面倒なのでNodeJSでちょちょちのちょいする。

const fs = require('fs');
const zlib = require('zlib');

var decoded = fs.readFileSync('base64data','utf8');
decoded = new Buffer(decoded,'base64');
var inflated;
try{
    while(true){
        inflated = zlib.inflateSync(decoded);
        console.log('Inflate OK');
        decoded = new Buffer(inflated.toString('utf8'),'base64');
        console.log('Decode OK');
    }
}catch(e){
    console.log(e);
    console.log(inflated.toString('utf8'));
    console.log(decoded.toString('utf8'));
}

Party

なにがパーティーだよ
outputは[(party:val)]の配列形式で記述されている。 val配列はparty配列にx=>f(x,coeff)を適用した値である。
coeffの1つ目の値は、常にsecretである。
つまり、

output[0] = (p , v)
          = (p , f(p,coeff) )

である。
f関数の実装は次のようになっている。

def f(x, coeff):
    y = 0
    for i in range(len(coeff)):
        y += coeff[i] * pow(x, i)
    return y

読み解くと

f(x,coeff) = coeff[0] * x⁰ + coeff[1] * x¹ + coeff[2] * x² + coeff[3] * x³ + ... + coeff[N] * xⁿ 

である。 これは、coeffを桁の値としたx進数の数値を作り出しているとも読み取れる。
ここで、coeff[0] = secret なので、f(p,coeff)をp進数としてみた時の1桁目の値がsecretになる。
N進数の1桁目はNで割った余りを求めればいいので、v % p を計算すればよい。

from Crypto.Util.number import long_to_bytes

party = # いっぱい
val = # たくさん

val = val % party 

print(long_to_bytes(val))

[warmup] Welcome

若者のIRC離れ

containers

お手元のバイナリエディタで開くと、ところどころにPNGのヘッダがある。
それぞれで抜き出してやるとフラグが書いた画像が出てくる。
f:id:pekko1215:20190526215126p:plain

const fs = require('fs');
const bsplit = require('buffer-split');
var txt = fs.readFileSync('./container');
var arr = bsplit(txt,Buffer.from('PNG'))


arr.map((d,i)=>{
    fs.writeFileSync('./out/'+i+'.png',Buffer.concat([Buffer.from([0x89]),Buffer.from('PNG'),d]));
})

Dump

fileコマンドで調べるとtcpdumpファイルだということがわかる。
WireSharkで開き、HTTPで絞りこむと、webshellというページにシェルコマンドを投げているのがわかる。 「hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag」というコマンドの応答を見ると、hexdumpした結果が返されている。
1Byteを8進数3桁で表しているので、気を付けながら複合していく。

const fs = require('fs');

var txt = fs.readFileSync('./hdump.txt','utf8');
// txt = `037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133
// 327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252`

var line = txt.split('\n');

var arr = [];

line.forEach(t=>{
    t.split(' ').forEach(n=>{
        arr.push(parseInt(n,8));
    })
})

var buff = Buffer.from(arr)
fs.writeFileSync('out',buff);

複合すると、gzip圧縮されたファイルができるので、解答する。
中の画像にフラグが書かれている。

Sliding puzzle

スライドパズルを解く問題、昔Wiiに画像からパズル作るゲームあったよね。

GitHub - cedricblondeau/sliding-puzzle: A sliding puzzle with a solver written in JavaScript using React for the view part and cat GIFs as your rewards.

こんな便利なプログラムがあるのでありがたく使う。

const net = require('net');
const client = net.Socket();

const Grid = require('./app/js/models/grid');
const Solver = require('./app/js/models/solver');

const MessageQueue = [];
var mesCallback = null;

const ArrowNumber = {
    UP:0,
    RIGHT:1,
    DOWN:2,
    LEFT:3
}

function mes(buff){
    buff = buff.toString('utf8');
    if(mesCallback){
        mesCallback(buff);
        mesCallback = null;
    }else{
        MessageQueue.push(buff);
    }
}

async function WaitMessage(){
    if(MessageQueue.length){
        return MessageQueue.pop();
    }else{
        return new Promise(r=>{
            mesCallback = r;
        })
    }
}

function parseGrid(text){
    var arr = text.replace(/-|\s/g,'').replace(/\|\|/g,'|').split('|').map(Number);
    arr.pop();
    arr.shift();
    arr = arr.map(n=>n == 0 ? '' : n);
    return new Grid(arr,
        arr.findIndex(d=>d===''),
        ['',...[...Array(8).keys()].map(d=>d+1)]);
}

client.connect('24912','133.242.50.201',async ()=>{
    client.on('data',mes);
    while(true){
    var message = await WaitMessage();
    console.log(message)
    var grid = parseGrid(message);
    var solver = new Solver(grid);
    console.log(grid)
    var solution = solver.solve();
    var ret = solution.map(d=>ArrowNumber[d]).join();
    console.log(ret)
    client.write(ret);
    }
})

こういう目標がはっきりしてる問題は楽ぴぃ

あとがき

pwnはむり、むずい