엄범


STDIO

Note ) 파이프와 STDIO는 다르다!
파이프는 키보드나 모니터같은 하드웨어 장치와 프로세스를 연결하는 방법이 아니라, 한 프로세스의 stdout을 다른 프로세스의 stdin으로 연결하는 IPC 방법이다. 따라서 STDIO는 pipe 구조로 이루어져 있는 것이 아니다! pipe에 접근하든 STDIO에 접근하든 file philosophy에 의해 `` fd``를 이용해 접근하기 때문에 착각할 수 있음.

파이프로 리다이렉션 하지 않는 경우 stdin, stdout, stderr은 모두 같은 character device를 가리키면서, read-only가 아니다.
따라서 `` stdin``을 이용해 출력하거나, `` stdout``을 이용해 데이터를 쓰는 것이 가능하다.
* stdio를 PIPE로 리다이렉션 하는 경우는 device를 가리키는게 아니라 PIPE를 가리키기 때문에 서로를 구분하게 된다.
```c
umbum:~/workspace/pwn/vdso (master) $ ll /dev/std*
lrwxrwxrwx 1 root root 15 Sep  2 05:01 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Sep  2 05:01 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Sep  2 05:01 /dev/stdout -> /proc/self/fd/1
umbum:~/workspace/pwn/vdso (master) $ ll /proc/self/fd
total 0
dr-x------ 2 ubuntu ubuntu  0 Sep  2 07:29 ./
dr-xr-xr-x 9 ubuntu ubuntu  0 Sep  2 07:29 ../
lrwx------ 1 ubuntu ubuntu 64 Sep  2 07:29 0 -> /dev/pts/1
lrwx------ 1 ubuntu ubuntu 64 Sep  2 07:29 1 -> /dev/pts/1
lrwx------ 1 ubuntu ubuntu 64 Sep  2 07:29 2 -> /dev/pts/1
```

fgets의 stdin 처리

```
fgets 호출 부분 :
0x080484d4 <main+80>:   sub    $0x4,%esp
0x080484d7 <main+83>:   pushl  0x8049788
0x080484dd <main+89>:   push   $0x400
0x080484e2 <main+94>:   lea    0xfffffae8(%ebp),%eax
0x080484e8 <main+100>:  push   %eax
0x080484e9 <main+101>:  call   0x804838c <_init+56>

(gdb) x/x 0x8049788
0x8049788 <stdin@@GLIBC_2.0>:   0x0083f720
(gdb) x/4x 0x0083f720
0x83f720 <_IO_2_1_stdin_>:      0xfbad2288      0xf6ffe006      0xf6ffe006     0xf6ffe000
```
* fgets 호출 이후 시점의 메모리 상태.

_IO_FILE structure

stdin 객체 `` <_IO_2_1_stdin_>``은 `` _IO_FILE`` type이다. `` _IO_FILE``은 /usr/include/libio.h에 정의되어 있다.

```c

struct _IO_FILE {

    int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ 

#define _IO_file_flags _flags

 

    /* The following pointers correspond to the C++ streambuf protocol. */ 

    /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ 

    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. */

         ...

}

```

첫번째는 flag, 

두번째는 현재 읽어야할 위치, 

세번째는 get area의 끝, 

네번째는 get area의 시작점이다.

get area가 흔히 말하는 입력 버퍼다.

( fgets 수행 이후에는 버퍼를 읽었기 때문에 두번째와 세번째값이 같아진다. )

stdin으로 넘긴 데이터는 네번째 entry ~ 세번째 entry 공간에 존재한다는 것을 알 수 있다.



입력 버퍼 초기화

fgets가 리턴하기 전에 stdin의 입력 버퍼 데이터를 초기화해주고 리턴하는게 맞는걸까? 아니면 퍼미션으로 제어해야 하는걸까?

사실 `` _IO_FILE``구조체는 fgets에서 인자로 넘어왔을 뿐이니까, fgets와는 관련이 없다.
또한 ``c fflush(), rewind(), while (getchar() != '\n');`` 같은게 입력 버퍼 초기화 방법으로 알려져 있지만, 사실 입력 버퍼 자체를 초기화 하는게 아니다.
이는 `` _ IO_FILE``의 현재 읽어야 하는 위치인 두번째 엔트리 값을 get area의 끝을 나타내는 세번째 엔트리 값과 동일하게 만드는 역할을 할 뿐이지, get area 자체를 초기화 하는 작업은 하지 않는다.
(fgets는 개행까지 읽는데, scanf 등을 사용할 때 끝에 개행문자가 남는 경우 사용하게 된다.)

정 초기화 하고 싶다면 직접 stdin 파일기술자를 이용해 데이터를 덮어 쓰거나  ``c memset()``을 사용할 수 있다.
아무튼 이런 저런 이유로 직접 초기화하기 보다는 퍼미션으로 제어하도록 되어 있는데, 실행 권한이 없더라도 고정된 위치에 data가 남는다는 점을 이용해 파라미터를 넘기는데 활용할 수 있다.
따라서 결국은 ASLR로 주소를 특정하지 못하게 하는 방법을 사용해야 한다.


fgets / read / argv

``c strcpy()``는 origin data에 `` 0x00``이 있는지만 따져보면 된다.
참고 ) ``c strcpy()`` 시 알아서 `` 0x00``을 넣어준다.

fgets

  • 입력 데이터에 0x0a가 있는지 따져보아야 한다.
  • EOF 또는 개행문자(0x0a)를 입력하지 않는 경우 계속 입력을 대기하기 때문에 특히 소켓을 사용할 때 끝에 ``c \n``을 보내주어야 한다.
    이 때 입력한 \n(0x0a) 까지 포함해서 받기 때문에 당연히 문자열 끝에 0x00이 들어갈 거라고 생각하면 안된다.
  • EOF와 개행문자(0x0a)만 인식하므로 공백(0x20)이나 0x00도 모두 받을 수 있다.

\x00

제대로 들어간다.

```bash

$ python -c 'print "abc"+"\x00"+"def"' | ./2

0xfee84cf0  61 62 63 00 64 65 66 0a 00 4d e8 fe 56 86 04 08   abc.def..M..V...

```


\x20

제대로 들어간다.

```bash

$ python -c 'print "abc"+"\x20"+"def"' | ./2

0xfee8b390  61 62 63 20 64 65 66 0a 00 b3 e8 fe 56 86 04 08   abc def.....V...

```


\x0a

\x0a까지만 들어간다.

```bash

$ python -c 'print "abc"+"\x0a"+"def"' | ./2

0xfefa6e90  61 62 63 0a 00 00 00 00 b8 6e fa fe 56 86 04 08   abc......n..V...

```


read

``c fgets()``와 같이 stdin으로 받지만, ``c read()``는 개행 문자와 관련 없이 최대한 읽어들인다.
즉, 모든 stdin이 개행까지 받는다고 생각하면 안된다. 개행까지 받는건 ``c gets()``계열의 특성이다.
  1. 버퍼에 있는 데이터가 `` nbytes`` 보다 작은 경우 모두 읽어오고 종료
  2. 버퍼에 있는 데이터가 `` nbytes`` 보다 큰 경우 `` nbytes``만큼 읽어오고 종료

```bash

$ python -c 'print "a\x0ab"' | ./gets
61 a 0 0 0
$ python -c 'print "a\x0ab"' | ./read
61 a 62 a 0
```

argv

  • 입력 데이터에 0x00, 0x20, 0x0a가 있는지 따져보아야 한다.

Note ) argv에 ``c \x00`` 넘기는 법 : ``bash ./aaa ""``

``c \x20 | \x0a``도 같은 방법으로 넘기면 된다.


\x00

\x00은 그냥 무시하고 끝까지 들어간다.

```bash

$ ./1 `python -c 'print "abc"+"\x00"+"def"'`

0xfef1cc41  61 62 63 64 65 66 00 48 4f 53 54 4e 41 4d 45 3d   abcdef.HOSTNAME=

```


\x20

\x20이 \x00으로 바뀌지만 끝까지 들어간다.

* `` \x20``을 argv 구분자로 사용하기 때문. 즉 ``c argv[1] == "abc", argv[2] == "def"``

```bash

$ ./1 `python -c 'print "abc"+"\x20"+"def"'`

0xfef4fc40  61 62 63 00 64 65 66 00 48 4f 53 54 4e 41 4d 45   abc.def.HOSTNAME

```


\x0a

\x0a가 \x00으로 바뀌지만 끝까지 들어간다.

* \x20과 같다.

```bash

$ ./1 `python -c 'print "abc"+"\x0a"+"def"'`

0xfef01c40  61 62 63 00 64 65 66 00 48 4f 53 54 4e 41 4d 45   abc.def.HOSTNAME

```