SYSTEM HACKING

stdin, stdout, stderr bss segment 할당 및 _IO_FILE

msh1307 2022. 7. 22. 20:13
1
int setvbuf(FILE *stream, char *buf, int type, size_t size);
cs

setvbuf 함수이다. 

FILE 구조체의 포인터, 사용자가 임의로 할당할 버퍼의 주소, 버퍼링 모드, 사이즈를 차례대로 받는다. 

1
2
3
4
5
6
#include <stdio.h>
int main(){
    setvbuf(stdout, 0, _IONBF, 0);
    printf("HI\n");
}
 
 
cs

CTF에서 주로 입력을 안 꼬이도록 하려고 stdin을 _IONBF로 설정하는 경우도 있다. 

stdout은 기본적으로 line buffering이지만 여기선 일부러 stdout을 setvbuf로 버퍼링 사용을 안 하도록 했다.

그래서 임시 버퍼가 heap에 할당되지 않는다. 

명시적으로 stdout을 사용해서 stdout이 bss에 위치한다.

puts 함수가 돌아가고 나서도 heap이 할당되지 않은 것을 볼 수 있다. 

stdout이 bss segment에 할당되어있다.

이 stdout은 _IO_2_1_stdout_ 구조체를 가리킨다.

_IO_2_1_stdout_과 stdout은 libc에 있다.

여기선 dynamic linker가 symbol을 보고 bss에 stdout을 넣어준것 같다.

이 stdout을 통해 libc leak을 하기도 한다.

libc 보고 오프셋 더해서 계산해서 읽어보니 stdout은 libc에 잘 있다.

 

setvbuf로 stdin이나 stdout 같은 표준 입출력 스트림 버퍼링을 제어할때 처럼 명시적으로 stdout, stdin, stderr를 사용하면 bss segment에 그 stdin, stdout, stderr가 위치한다.

 

만약 따로 명시적으로 stdout을 사용하지 않고, printf나 puts같은 함수를 통해 암시적으로 쓰이면 stdout은 bss segment에 위치하지 않는다.

생각해보면 libc에서 puts나 printf가 돌아가는데 그냥 libc에 있는 stdout을 쓰면 되니까 굳이 바이너리에 포함될 필요가 없다.

1
2
3
4
#include <stdio.h>
int main(){
    printf("HI\n");
}
cs

암시적으로 stdout이 사용되는 printf 함수를 사용했다. 

puts 함수로 바뀌었지만 상관없다.

따로 설정하지 않았다면 puts는 heap에 임시 버퍼를 만든다.

bp를 puts 호출전에 걸었다. 아직은 heap에 임시 버퍼가 할당되지 않았다.

puts 이후 heap이 할당된 것을 볼 수 있다.

그리고 이 heap에 chunk중에 0x410의 chunk size를 가진 임시 버퍼를 확인할 수 있다.

stdout이 기본적으로 line buffering이라 0x0a가 뒤에 붙어있는 것을 확인할 수 있다.

이때 bss segment를 보면 stdin이나 stdout이 없는 것을 확인할 수 있다.

libc에 data segment에 stdout이 있다. 

_IO_2_1_stdout_ 구조체이다. 

_IO_FILE 구조체를 확인해보면 된다.

 

glibc struct_FILE.h에 _IO_FILE 구조체가 정의되어있다. 

1
typedef struct _IO_FILE FILE;
cs

struct _IO_FILE이 FILE로 정의되어있다.

FILE 포인터가 이 구조체를 가리키는 포인터라고 보면 된다.

실제로는 stdout은 _IO_FILE_plus라는 구조체로 한번더 감싸져있긴 하지만 상관없다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
};
 
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Magic number and bits for the _flags field.  The magic number is
   mostly vestigial, but preserved for compatibility.  It occupies the
   high 16 bits of _flags; the low 16 bits are actual flag bits.  */
#define _IO_MAGIC         0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK    0xFFFF0000
#define _IO_USER_BUF          0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED        0x0002
#define _IO_NO_READS          0x0004 /* Reading not allowed.  */
#define _IO_NO_WRITES         0x0008 /* Writing not allowed.  */
#define _IO_EOF_SEEN          0x0010
#define _IO_ERR_SEEN          0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close.  */
#define _IO_LINKED            0x0080 /* In the list of all open files.  */
#define _IO_IN_BACKUP         0x0100
#define _IO_LINE_BUF          0x0200
#define _IO_TIED_PUT_GET      0x0400 /* Put and get pointer move in unison.  */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING      0x1000
#define _IO_IS_FILEBUF        0x2000
                           /* 0x4000  No longer used, reserved for compat.  */
#define _IO_USER_LOCK         0x8000
 
cs

_flags이다.

보면 멤버 변수 _IO_read_ptr 부터 쭉 어떤 주소가 보인다.

그 주소가 아까 heap에 할당되었던 임시 버퍼의 주소이다.

_IO_buf_end랑 _IO_buf_base를 빼면 heap chunk header 0x10 제외하고 0x400이라는 크기가 제대로 나오는 것을 확인할 수 있다.