1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SECCON CTF for Begginer 2024 WriteUp

Posted at

はじめに

少し遅くなってしまいましたが,2024/06/15から16にかけて行われたSECCON BEGINNERS CTF 2024のWriteupを書いていきたいと思います.   

当初の目標としては難易度beginnrsを全部解けたらいいなとか思ってたんですが現実はそんなに甘くなく結局2問しか解けず,結果は513位と散々な結果でした.  

しかし去年は1問しか解けてない(去年はチームで参加して解いたのは僕ではないので実質0問)のに対して今年は2問解けたので多少は成長してるかなと思います.
image.png

simpleoverflow

以下のようなソースコードが配布されます.

src.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[10] = {0};
  int is_admin = 0;
  printf("name:");
  read(0, buf, 0x10);
  printf("Hello, %s\n", buf);
  if (!is_admin) {
    puts("You are not admin. bye");
  } else {
    system("/bin/cat ./flag.txt");
  }
  return 0;
}

__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(120);
}

このコードのおかしいところは配列bufが要素数10で定義されているので,10バイト分確保されていますが,read関数では0x10(16バイト)分読み込むようになっています.
定義の順番からしても配列bufの後にis_adminが定義されているので入力する際にオーバーフローを狙っていけそうですね.

目標としてはis_adminの中身を1にすればいいので\x1を書き込めればいいと思います.またis_adminint型で定義されているので4バイト確保されています.

なので入力する文字列としては,bufに書き込む10バイト分はなんでもいいのでとりあえずAAAAAAAAAAとかにしときます.問題のis_adminに書き込む内容ですがリトルエンディアンで書き込む必要があるため\x1\x0\x0\x0とする必要があります.

なので最終的にはAAAAAAAAAA\x1\x0\x0\x0を入力すればflagがgetできます.

getRank

配布されるmain.tsの中身の一部です.

main.ts
const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];

type Res = {
  rank: number;
  message: string;
};

function ranking(score: number): Res {
  const getRank = (score: number) => {
    const rank = RANKING.findIndex((r) => score > r);
    return rank === -1 ? RANKING.length + 1 : rank + 1;
  };

  const rank = getRank(score);
  if (rank === 1) {
    return {
      rank,
      message: process.env.FLAG || "fake{fake_flag}",
    };
  } else {
    return {
      rank,
      message: `You got rank ${rank}!`,
    };
  }
}

function chall(input: string): Res {
  if (input.length > 300) {
    return {
      rank: -1,
      message: "Input too long",
    };
  }

  let score = parseInt(input);
  if (isNaN(score)) {
    return {
      rank: -1,
      message: "Invalid score",
    };
  }
  if (score > 10 ** 255) {
    // hmm...your score is too big?
    // you need a handicap!
    for (let i = 0; i < 100; i++) {
      score = Math.floor(score / 10);
    }
  }

  return ranking(score);
}

変数rankが1の時にFlagがGETできるみたいですね.
rankの求め方はconst RANKING = [10 ** 255, 1000, 100, 10, 1, 0];のどの値よりも大きいかで決まるみたいですね.

*例えばScoreが1001だったら10 ** 255よりも小さく1000よりも大きいのでrankは2になります.なので10 ** 255よりも大きい値を入力すればいいのですが以下のような条件があるため,これではうまくいきません.

  • score>10**255よりも大きい
    • この場合入力したスコアよりも減点されてしまいます
  • 入力文字数が300文字より大きい
    • この場合-1になってしまいます.

ここでinputparseIntを適用してscoreに渡されていることがわかります.  

リファレンスによると入力された文字列に0xが含まれている場合,16進数と解釈されるようです.  

なのでBurpsuite使って,0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFみたいな感じで300文字超えない程度に送信すればOKです.

最後に

多少は考え方がわかるようになってきた気がしますが,まだまだ実力不足を感じました.

特にcryptoやreverseなどはほとんど何も知らないので少しずつ勉強していきたいです.

次はWani CTFですね.どのくらい参加できるかわかりませんが参加される皆さん頑張りましょう!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?