Journey to Security/C언어

C 언어 배열의 문자열 수정과 strcpy의 동작 원리 (+strncpy)

Cordilog 2026. 2. 24. 18:15

지난 포스팅에서 배열의 이름이 '상수 주소'이기 때문에 새로운 문자열을 직접 대입(ch = "...")할 수 없다는 것을 확인했다. 그렇다면 이미 만들어진 배열의 내용을 바꾸려면 어떻게 해야 할까?

정답은 메모리 공간에 값을 하나씩 복사해 넣는 strcpy 함수를 사용하는 것이다.

 

1. 코드

지난 포스팅에서 사용한 코드에 string.h 헤더를 추가하고 strcpy를 통해 배열의 내용을 변경하는 과정을 추가한다.

#include <stdio.h>
#include <string.h> // strcpy 함수 사용을 위해 필요

int main() {
    char ch[10] = "ABCDEF"; // 여유 공간을 위해 크기를 10으로 설정
    char *ptr = "123456";

    printf("--- 변경 전 ---\n");
    printf("Array ch address: %p, value: %s\n", (void*)ch, ch);

    // ch = "HIJKL"; // 에러 발생: 배열 이름은 상수 주소임
    strcpy(ch, "HIJKL"); // 성공: 배열 공간에 값을 복사함

    printf("\n--- 변경 후 ---\n");
    printf("Array ch address: %p, value: %s\n", (void*)ch, ch);

    return 0;
}

 

2. strcpy의 동작 원리

strcpy(dest, src) 함수는 다음과 같은 순서로 동작한다.

  1. 루프 시작: 소스 문자열(src)의 첫 번째 문자부터 하나씩 읽는다. (바이트(Byte) 단위로 복사)
  2. 복사: 읽어온 문자를 목적지 배열(dest)의 대응하는 위치에 집어넣는다.
  3. 종료 조건: 소스 문자열에서 널 문자(\0)를 만날 때까지 이 과정을 반복한다.
  4. 널 문자 삽입: 마지막에 널 문자까지 복사하여 문자열의 끝을 알린다.

결과적으로 배열 ch가 가리키는 메모리 주소는 절대 변하지 않는다.

그 주소에 들어있는 데이터(내용물)만 바뀔 뿐이다.

 

 

3. GDB로 strcpy 전후 메모리 관찰하기

GDB를 통해 strcpy 실행 전후의 메모리를 들여다보면 주소가 변하지 않는 것을 확인할 수 있다.

실습을 위해 코드를 디버깅 정보와 함께 컴파일(gcc -g)한 후, 아래 순서대로 명령어를 입력한다.

① 실행 및 중단점 설정

gdb ./arrayassign
(gdb) list          # 코드 확인
(gdb) break 10      # strcpy 실행 직전 라인에 중단점 설정
(gdb) run           # 프로그램 실행

② strcpy 실행 전 상태 확인

strcpy가 호출되기 전, 현재 배열의 주소와 값을 기록한다.

  • p &ch : 배열의 시작 주소를 확인한다. (예: 0x7fffffffdd41)
  • x/s ch : 현재 배열에 저장된 문자열을 확인한다. ("ABCDEF")
  • x/2xw ch : 메모리 내부 비트 값을 16진수로 확인한다.

③ 단계 실행 (next)

next 명령어를 통해 strcpy 함수를 한 줄 실행한다.

(gdb) next

④ strcpy 실행 후 변화 확인

값이 바뀌었음에도 주소가 그대로인지 확인한다.

  • p &ch : 주소가 이전과 동일한지 확인한다. (여전히 0x7fffffffdd41이어야 함)
  • x/s ch : 문자열이 "HIJKL"로 바뀌었는지 확인한다.
  • x/2xw ch : 내부의 16진수 값이 새 문자열의 ASCII 값으로 교체되었는지 확인한다.

 

 

4. 주의사항: Buffer Overflow

strcpy를 사용할 때 가장 주의해야 할 점은 배열의 크기다.

만약 ch 배열의 크기는 10인데, 복사하려는 문자열이 20글자라면 배열의 경계를 넘어서는 Buffer Overflow가 발생한다. 이는 프로그램의 예기치 못한 종료나 보안 취약점의 원인이 된다.

따라서 실제 개발에서는 복사할 길이를 제한하는 strncpy를 사용하는 권장된다.

 

5. strncpy : 더 안전한 방법

strcpy는 소스 문자열의 길이를 따지지 않고 널 문자를 만날 때까지 무조건 복사한다.

만약 배열의 크기보다 긴 문자열을 복사하면 인접한 메모리를 덮어쓰는 버퍼 오버플로우(Buffer Overflow) 공격에 취약해진다.

이를 방지하기 위해 복사할 최대 길이를 제한하는 strncpy 사용 방법을 알아보자.

strncpy의 사용법과 특징

strncpy는 세 번째 인자로 복사할 문자의 최대 개수(n)를 받는다.

#include <string.h>

char ch[10];
// strncpy(목적지, 소스, 최대_복사_길이);
strncpy(ch, "VeryLongString", sizeof(ch) - 1);
ch[sizeof(ch) - 1] = '\0'; // 안전을 위해 마지막에 널 문자 수동 삽입
  1. 길이 제한: n바이트만큼만 복사하므로 배열의 범위를 벗어나는 것을 막을 수 있다.
  2. 널 문자의 함정: 만약 소스 문자열의 길이가 n보다 크거나 같으면, 목적지 문자열 끝에 널 문자(\0)가 붙지 않는다. 이 상태로 문자열을 출력하면 메모리의 다른 부분까지 읽어버리는 오류가 발생할 수 있다.
  3. 안전한 관례: 항상 sizeof(dest) - 1만큼만 복사하고, 마지막 인덱스에 직접 \0을 넣어주는 것이 가장 안전하다.

GDB에서 strncpy 확인하기

strncpy를 사용한 후 GDB로 메모리를 확인하면, 지정한 길이 이후의 메모리는 건드리지 않는 것을 볼 수 있다.

(gdb) p ch
$1 = "VeryLongS", '\000'  # 10바이트 중 9바이트만 복사되고 마지막은 널 문자