초기화되지 않은 변수

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

기본 정보

핵심 아이디어는 초기화되지 않은 변수는 해당 메모리 주소에 이미 있던 값을 갖게 된다는 사실을 이해하는 것이다. 예:

  • Function 1: initializeVariable: 변수 x를 선언하고 값(예: 0x1234)을 할당한다. 이 동작은 메모리의 한 자리를 예약하고 특정 값을 넣는 것과 같다.
  • Function 2: useUninitializedVariable: 여기서는 또 다른 변수 y를 선언하지만 값을 할당하지 않는다. C에서는 초기화되지 않은 변수가 자동으로 0으로 설정되지 않는다. 대신 해당 메모리 위치에 마지막으로 저장되어 있던 값을 그대로 유지한다.

이 두 함수를 순차적으로 실행하면:

  1. initializeVariable에서 x에 값(0x1234)이 할당되며, 이는 특정 메모리 주소를 차지한다.
  2. useUninitializedVariable에서는 y가 선언되지만 값이 할당되지 않아 x 바로 다음 메모리 위치를 차지한다. y를 초기화하지 않았기 때문에 그 메모리 위치에 마지막으로 남아 있던 값(즉 x가 사용한 값)을 ’상속’받게 된다.

이 동작은 저수준 프로그래밍의 핵심 개념을 보여준다: 메모리 관리가 중요하다는 것. 초기화되지 않은 변수는 메모리에 남아 있던 민감한 데이터를 의도치 않게 보유할 수 있어 예측 불가능한 동작이나 보안 취약점으로 이어질 수 있다.

초기화되지 않은 스택 변수는 다음과 같은 여러 보안 위험을 초래할 수 있다:

  • Data Leakage: 비밀번호, 암호화 키 또는 개인 정보 같은 민감한 정보가 초기화되지 않은 변수에 남아 있으면 노출될 수 있으며, 공격자가 해당 데이터를 읽을 수 있다.
  • Information Disclosure: 초기화되지 않은 변수의 내용은 프로그램의 메모리 레이아웃이나 내부 동작에 대한 정보를 드러내어 공격자가 표적화된 익스플로잇을 개발하는 데 도움을 줄 수 있다.
  • Crashes and Instability: 초기화되지 않은 변수를 사용하는 연산은 정의되지 않은 동작을 일으켜 프로그램 충돌이나 예측 불가능한 결과를 초래할 수 있다.
  • Arbitrary Code Execution: 특정 시나리오에서는 공격자가 이러한 취약점을 이용해 프로그램의 실행 흐름을 변경하여 임의의 코드를 실행할 수 있으며, 이는 원격 코드 실행 위협을 포함할 수 있다.

예제

#include <stdio.h>

// Function to initialize and print a variable
void initializeAndPrint() {
int initializedVar = 100; // Initialize the variable
printf("Initialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&initializedVar, initializedVar);
}

// Function to demonstrate the behavior of an uninitialized variable
void demonstrateUninitializedVar() {
int uninitializedVar; // Declare but do not initialize
printf("Uninitialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&uninitializedVar, uninitializedVar);
}

int main() {
printf("Demonstrating Initialized vs. Uninitialized Variables in C\n\n");

// First, call the function that initializes its variable
initializeAndPrint();

// Then, call the function that has an uninitialized variable
demonstrateUninitializedVar();

return 0;
}

How This Works:

  • initializeAndPrint Function: 이 함수는 정수 변수 initializedVar를 선언하고 값 100을 대입한 뒤, 변수의 메모리 주소와 값을 출력합니다. 이 단계는 단순하며 초기화된 변수가 어떻게 동작하는지 보여줍니다.
  • demonstrateUninitializedVar Function: 이 함수에서는 정수 변수 uninitializedVar를 초기화하지 않고 선언합니다. 값을 출력하려 하면 출력이 무작위 숫자를 보일 수 있습니다. 이 숫자는 해당 메모리 위치에 이전에 있던 데이터를 나타냅니다. 환경과 컴파일러에 따라 실제 출력은 달라질 수 있고, 일부 컴파일러는 안전을 위해 변수를 자동으로 0으로 초기화할 수도 있지만, 이를 신뢰해서는 안 됩니다.
  • main Function: main 함수는 위 두 함수를 순서대로 호출하여 초기화된 변수와 초기화되지 않은 변수의 차이를 보여줍니다.

Practical exploitation patterns (2024–2025)

고전적인 “read-before-write” 버그는 여전히 유효합니다. 현대의 완화책(ASLR, canaries)이 종종 비밀 유지에 의존하기 때문입니다. 일반적인 공격 표면:

  • Partially initialized structs copied to userland: 커널이나 드라이버는 종종 길이 필드만 memset 하고 copy_to_user(&u, &local_struct, sizeof(local_struct))를 호출합니다. 패딩과 사용되지 않은 필드는 stack canary halves, saved frame pointers 또는 kernel pointers를 leak 합니다. 구조체에 function pointer가 포함되어 있다면, 이를 초기화하지 않으면 나중에 재사용될 때 controlled overwrite를 허용할 수도 있습니다.
  • Uninitialized stack buffers reused as indexes/lengths: 초기화되지 않은 size_t len;read(fd, buf, len)의 경계를 정하는 데 사용되면, 해당 스택 슬롯이 이전 호출의 큰 값을 유지하고 있을 때 공격자가 범위를 벗어난 읽기/쓰기(out-of-bounds reads/writes)를 하거나 크기 검사를 우회할 수 있습니다.
  • Compiler-added padding: 개별 멤버가 초기화되어 있어도 그 사이의 암묵적 패딩 바이트는 초기화되지 않습니다. 구조체 전체를 userland로 복사하면 이전 스택 내용(canaries, pointers)을 포함하는 패딩이 leak 됩니다.
  • ROP/Canary disclosure: 함수가 디버깅을 위해 로컬 구조체를 stdout으로 복사하면, 초기화되지 않은 패딩이 stack canary를 드러내어 이후의 stack overflow 공격을 brute-force 없이 가능하게 할 수 있습니다.

Minimal PoC pattern to detect such issues during review:

struct msg {
char data[0x20];
uint32_t len;
};

ssize_t handler(int fd) {
struct msg m;              // never fully initialized
m.len = read(fd, m.data, sizeof(m.data));
// later debug helper
write(1, &m, sizeof(m));   // leaks padding + stale stack
return m.len;
}

완화책 및 컴파일러 옵션 (우회 시 염두에 둘 것)

  • Clang/GCC auto-init: Recent toolchains expose -ftrivial-auto-var-init=zero or -ftrivial-auto-var-init=pattern, filling every automatic (stack) variable at function entry with zeros or a poison pattern (0xAA / 0xFE). 이는 함수 진입 시 모든 자동(stack) 변수를 0 또는 패턴으로 채워 대부분의 uninitialized-stack info leaks를 차단하고, 비밀값을 알려진 값으로 바꿔 exploitation을 더 어렵게 만듭니다.
  • Linux kernel hardening: Kernels built with CONFIG_INIT_STACK_ALL or the newer CONFIG_INIT_STACK_ALL_PATTERN zero/pattern-initialize every stack slot at function entry, wiping canaries/pointers that would otherwise leak. 이러한 옵션으로 빌드된(특히 Clang으로 빌드된) 커널을 배포하는 배포판을 찾아보세요(6.8+ 하드닝 구성에서 흔함).
  • Opt-out attributes: Clang now allows __attribute__((uninitialized)) on specific locals/structs to keep performance-critical areas uninitialized even when global auto-init is enabled. 이러한 어노테이션은 성능 상 중요한 영역을 의도적으로 초기화하지 않기 위해 사용되므로 주의 깊게 검토하세요—종종 side channels을 노린 공격 표면을 표시합니다.

공격자 관점에서는 바이너리가 이러한 플래그로 빌드되었는지 여부가 stack-leak primitives가 실용적인지, 아니면 heap/data-section disclosures로 전환해야 하는지를 결정합니다.

Finding uninitialized-stack bugs quickly

  • 컴파일러 진단(Compiler diagnostics): Build with -Wall -Wextra -Wuninitialized (GCC/Clang). For C++ code, clang-tidy -checks=cppcoreguidelines-init-variables will auto-fix many cases to zero-init and is handy to spot missed locals during audit.
  • 동적 도구(Dynamic tools): -fsanitize=memory (MSan) in Clang or Valgrind’s --track-origins=yes reliably flag reads of uninitialized stack bytes during fuzzing. 테스트 하니스에 이 도구들을 적용하면 미세한 padding leaks를 드러낼 수 있습니다.
  • Grepping 패턴(Grepping patterns): 코드 리뷰 시 전체 구조체를 대상으로 하는 copy_to_user / write 호출이나, 구조체의 일부만 설정된 상태에서의 memcpy/send 같은 stack 데이터 전송을 검색하세요. 초기화가 건너뛰어지는 에러 경로에 특히 주의하세요.

ARM64 예시

ARM64에서는 로컬 변수가 역시 stack에서 관리되므로 전혀 달라지지 않습니다. 이는 check this example에서 확인할 수 있습니다.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기