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을 붙였지만, 경계가 없어 오버플로가 구조적으로 발생 가능. 절대 사용 금지.