io & file fuction shortcut
계속 pwn 할때마다 아 scanf 함수의 delim이 뭐였지? 끝에 null을 넣어줬나? 화이트스페이스가 뭐였지? 가 너무 햇갈려서 이참에 모든 함수를 정리해보려고 한다.
참고로 수정해야 하는 개념이 있다면, white space는 공백을 말한다. 즉, \x20 \t 와 같은 공백 바이트들을 말하는데 이는 scanf 계열 함수에서 우리가 입력할때 이러한 white space가 어떻게 처리되는지에 대해서만 사용되는 개념이다.
따라서 이 다른 함수에서는 없는 개념이며, 이를 알고 넘어가는 것이 좋다.
따라서 출력함수가 멈추는 바이트나 입력함수가 끊기는 바이트를 end_space 라고 하겠다.
| 함수(군) | 끝에 붙는 바이트 | end_space | 추가 정보 |
|---|---|---|---|
| read(2) | 없음 | [] (요청크기/EOF/가용) | POSIX read는 요청한 바이트만 버퍼에 복사. 종결자/개행 따로 안 붙임. |
| write(2) | 없음 | [] (end_space 없음: 길이로 결정) | write는 넘겨준 버퍼 그대로 기록. 아무것도 추가 안 함. |
| fread | 없음 | [] (요청크기/EOF/가용) | 바이트 덩어리 그대로 읽고 저장. 종결자 안 붙음. |
| fwrite | 없음 | [] (end_space 없음: 길이로 결정) | 바이트 덩어리 그대로 씀. 아무것도 추가 안 함. |
| getc/fgetc/getchar | 없음 | [] ( 단순 1바이트 ) | 1바이트 그대로 읽음. 공백도 특별취급 없음. |
| fgets | [\x00] (NUL 종결) | [0A](LF) 또는 버퍼 한계 또는 EOF | 최대 n-1바이트 또는 개행을 포함해 읽고, 문자열 끝에 NUL 추가. → \n을 그대로 저장(여유 있으면) 즉, null로 overflow가 안남 |
| getline/getdelim | [\x00] (NUL 종결) | getline > [0A](LF) 또는 EOF getdelim > [delim] 또는 EOF | 구분자(기본은 \n) 자체를 버퍼에 포함하고 끝에 NUL 추가. 필요시 버퍼 malloc/realloc. |
| scanf 계열(전체 규칙) | 포맷별 상이(아래 B절) | 포맷별 상이(아래 B절) | |
| printf/fprintf/dprintf | 없음 | [\x00] | 서식화한 바이트를 스트림으로 씀. NUL/개행 자동 추가 없음. |
| sprintf (문자열 버퍼) | [\x00] | [\x00] | 결과 문자열 NUL 종결. (버퍼 크기 충분해야 함) |
| snprintf (문자열 버퍼) | [\x00] (단, size>0일 때) | [\x00] | 최대 size-1바이트 기록 + NUL 종결(size가 0이면 아무것도 안 쓰고 종결자도 없음). 반환값은 “필요했던 길이”. |
| fputs | 없음 | [\x00] | 문자열 그 자체만 씀(NUL은 쓰지 않음), 개행 자동 추가 안 함. |
| puts | [\x0A] (개행) | [\x00] | 문자열 출력 뒤에 개행 추가. NUL은 쓰지 않음. |
| putc/fputc/putchar | 없음 | [] | 지정한 1바이트만 씀(전달값이 '\n'이면 그걸 쓰는 것일 뿐). |
scanf 계열
white space = [\x09,\x0A,\x0B,\x0C,\x0D,\x20]
| 변환 | 리딩 전에 공백 skip? | 저장 형태/종결 | 토큰 경계(어디서 멈추나) end_space | 요점 |
|---|---|---|---|---|
| %d, %i, %u, %o, %x 등 정수 | 예 (isspace) | 숫자형 변수에 저장 (NUL 없음) | 포맷에 맞지 않는 첫 문자 | 대부분 변환은 선행 공백 자동 skip. |
| %f, %e, %g, %a 등 실수 | 예 | 부동소수 변수 | 포맷 미일치 | 동상. |
| %s | 예 | 문자열 + NUL 종결 | 다음 화이트스페이스에서 정지 | 필드폭 없으면 오버런 주의. POSIX %ms 쓰면 malloc해줌. |
| %c (필드폭 w, 기본 1) | 아니오 | 문자들 그대로(종결자 없음) | 정확히 w개 채우면 정지 | 앞의 개행까지도 그대로 읽음. 공백을 먼저 버리려면 포맷에 스페이스 " %c"를 두라. |
| %[ 스캔셋 | 아니오 | 문자열 + NUL 종결 | 셋에 없는 첫 문자 또는 필드폭 | [^...]로 부정셋. 역시 공백 안 건너뜀(필요하면 포맷에 스페이스 추가). |
| %p | 예 | 포인터 값 | 포맷 미일치 | 구현 규칙에 따름. |
| %n | 아니오 | 지금까지 소비한 문자 수(정수) | N/A | 입력 소비 안 함, 공백도 안 건너뜀. |
| (POSIX) %m 길이 수정자 | (변환에 따름) | %ms, %m[은 동적할당 + NUL 종결, %mc는 종결 없음 | (변환에 따름) | glibc 옛 %a 확장(동적 문자열)은 C99 %a(hex float)와 충돌해 POSIX %m로 대체. |
리딩 전에 공백 skip? 이라는 뜻은 예를 들어
scanf("%d" , &int ) 일때 내가
\x20\x2050
을 입력해도 50이 정상적으로 들어간다는 말 → 앞에 \x20 같은 화이트 스페이스가 무시됨
참고 예시
C
#include <stdio.h>
int main(void){
int x; char c1, c2;
scanf("%d", &x); // 사용자가 "123⏎" 입력
scanf("%c", &c1); // 바로 직전의 '\n'을 c1로 읽어버림
scanf(" %c", &c2); // ← 포맷의 공백이 선행 공백 전부 skip → 다음 비공백 1글자
printf("c1=0x%02X, c2=0x%02X\n", (unsigned char)c1, (unsigned char)c2);
}bof by null
함수에서 끝에 \x00 바이트를 붙여줘서 bof가 일어나는 경우가 더럿 있다. 이건 다음과 같은 경우다
NUL을 붙이는 성질 + 경계 미검사 때문에 실제로 오버플로 위험이 대표적으로 생기는 함수/패턴:
- sprintf/vsprintf(크기 인자 없음·무제한 포맷 기록) → 위험. snprintf를 써서 크기 제한을 주면 완화(단, size>0일 때만 NUL 보장).
- strcpy/strcat(경계 미검사, 마지막에 NUL 붙임) → 위험. strcat은 붙인 뒤 NUL을 추가하므로 공간 부족 시 바로 오버플로.
- scanf의 %s/%[를 필드폭 없이 작은 배열로 받을 때 → 위험. (POSIX 확장 %ms/%m[으로 동적할당 사용하거나, 필드폭 지정 권장)
- (역사적) gets → 본질적으로 위험하며 C11에서 표준에서 제거. 개행은 저장하지 않고 끝에 NUL을 붙였지만, 경계가 없어 오버플로가 구조적으로 발생 가능. 절대 사용 금지.