Visual Studio 프로젝트 옵션 중 Basic Runtime Checks가 있다.

이 옵션은 개발자가 저지르기 쉬운(하지만 찾기 어려운) 버그를 런타임 과정에서 찾아주는 기능이다.

 

프로젝트 설정의 C/C++ → Code Generation에서 설정이 가능하다. 

그림 1.  Basic Runtime Checks 옵션 설정 창

그림 1과 같이 Defualt, /RTCs, /RTCu, RTC1 중 하나를 선택할 수 있다.

본 문서에서는 각 옵션 별 동작을 설명한다.

1. Stack Frames (/RTCs)

이 설정은 버퍼 오버플로우 발생 여부를 체크한다.

버퍼 오버플로우는 단순히 프로그램의 오동작을 일으킬 뿐만 아니라 버퍼 오버런 공격에 사용될 수도 있으므로 매우 유의해야 하지만

개발 과정에서 놓치기 쉽고, 디버깅도 쉽지 않은 버그 중 하나이다.

 

다음은 고의로 버퍼 오버플로우를 발생시킨 상황이다.

[그림 2. 버퍼 오버플로우 고의 발생]

그림 2를 보면 pszMsg 배열에 할당된 메모리 주소는 0x3efcb0 ~ 0x3ecb2까지 3바이트인데 0x3ecb3도 00으로 설정이 된 걸 볼 수 있다. 이렇게 버퍼 오버플로우가 발생하면 이후 프로그램의 정상 동작을 보장하기 어렵게 된다.

 

'/RTCs' 옵션은 이러한 오버 플로우 발생을 방지한다.

'/RTCs'를 설정할 경우 지역변수 선언 시 스택에서 해당 변수 앞뒤의 메모리를 0xCC로 변경하고 이후 그 값이 변경되었는지를 검사한다.

만약 값이 변경이 되면 오버런 또는 언더런이 발생했다고 판단하고 예외를 발생시킨다.

[그림 3. 변수 생성 시 메모리 초기화]

그림 3을 보면 지역변수 pszMsg의 앞뒤로 메모리가 0xCC로 초기화되는 것을 확인할 수 있다.

[그림 4. 오버 플로우 발생 시 메모리]

그림 4에서 pszMsg에 할당된 메모리를 벗어난 0x10ffd67 영역이 0으로 변경되었고, 프로그램을 이를 감지하여 예외를 발생시킨다.

[그림 5. 버퍼 오버플로우 중단 창]

2. Uninitialized Variables (/RTCu)

이 설정은 초기화하지 않은 변수를 사용하고 있는지 체크한다.

/RTCu 설정을 하지 않으면 초기화되지 않은 변수를 사용하더라도 (운이 좋으면)프로그램은 동작하는데

설정을 할 경우에는 바로 예외를 발생시킨다.

3. Both (/RTC1, equiv. to /RTCsu)

이 설정은 /RTCs와 /RTCu 옵션을 모두 사용하는 설정이다.

4. Default

Default를 설정할 경우 모든 Runtime Check를 사용하지 않는다.

Windows 8 이상에서는 '전체 종료(full shutdown)', '빠른 부팅 종료(fast startup)', '절전모드(hybernate)' 세 가지로 시스템을 종료할 수 있다.

시스템 종료 방법 설명
full shutdown 시스템을 완전히 종료한다. 장기간 PC를 사용하지 않을 경우 전체 종료를 권장한다.
fast startup 빠른 부팅이 가능한 종료를 진행한다. hybernate보다 부팅이 빠르다.
hybernate laptop 환경을 고려하여 디자인 되었으며, 적은 전력을 사용하도록 sleep 상태로 들어간다.

언뜻 생각하기에 '시스템 종료' 버튼을 누를 경우 시스템 전체가 종료(full shutdown)될 것 같지만,

Windows 8 이상에서는 기본적으로 fast startup으로 종료된다. 제어판 '전원 옵션'에서 '빠른 시작 켜기'를 체크 해제해야 full shutdown을 한다.

 

fast startup으로 재부팅할 경우 서비스가 종료하지 않고 멈추었다가 부팅 후 동작을 이어가게 되는 등 시스템 종료 방법에 따라 일부 동작의 차이가 발생할 수 있다.

 

만약 마지막 부팅이 fast startup으로 부팅이 되었는지를 확인하고 싶다면 PowerShell에서 아래 명령을 실행하면 된다.

Get-WinEvent -ProviderName Microsoft-Windows-Kernel-boot -MaxEvents 10 | Where-Object {$_.id -like “27”}

위 명령을 실행하면 부팅 유형 값이 출력되는데 각 값은 아래와 같다.

부팅 유형 설명
0x0 cold boot from full shutdown
0x1 hybrid boot(fast startup)
0x2 resume from hibernate

 

상황에 따라, 환경에 따라 여러가지 형태의 path 존재할  있다.

C:\Windows\System32
/usr/bin
https://www.naver.com

여러 형태의 path  유독 윈도우만 디렉토리 구분자로 백슬래시(backslashs, ‘\’) 사용하고   다른 모든 환경에서는 슬래시(forward slashs, ‘/‘) 사용한다.  Windows에서만 백슬래시를 사용하는 것일까.

 

Windows에서 백슬래시를 사용하게  배경

1970년경, Unix 슬래시를 디렉토리 구분자로 소개했다.  슬래시를 선택했는지는 모른다.

그리고 1981, Windows MS-DOS 1.0 발표하였다. MS-DOS 1.0 나도 경험한 적이 없기에  모르지만 놀랍게도 디렉토리 개념이 없었다고 한다. 그리고 슬래시를 옵션값 설정용으로 사용을 했다. 이러한 기능은 현재도 유효하다. CMD에서 dir/w 입력하면 가로 목록 형식으로 출력되는 것을 확인할  있다.('help dir'을 입력하면 슬래시를 이용한 여러 옵션 값들을 확인할 수 있다.)

도스에서 '/'의 기능

이후 MS-DOS 2.0에서 디렉토리를 지원하기 시작하였고, 디렉토리 구분자가 필요하게 되었다. 그러나 Unix에서와 같이 슬래시를 디렉토리 구분자로 사용하면 MS-DOS 1.0 옵션 구분자와 충돌이 발생하여 사용할  없었다. 그때라도 옵션 구분자를 다른 문자로 변경했다면 문제가 없었겠지만 MS 디렉토리 구분자를 다른 문자로 바꾸어 사용하기 시작했다. 그  변경한 디렉토리 구분자가 백슬래시이다.

 

OS 따라 디렉토리 구분자를 정확히 써야만 하는가?

최근의 많은 소프트웨어들은  가지 모두를 호환하고 있다. 

Windows 탐색기에서 C:/Windows/System32 입력하면 슬래시가 백슬래시로 변경되어 검색된다.

Chrome 브라우저에서 백슬래시로 URL 입력해도 슬래시로 변경되어 검색된다.

이렇듯 많은 소프트웨어가 디렉토리 구분자로 슬래시와 백슬래시를 모두 지원하고 있다. (테스트 결과 Unix계열에서는 백슬래시를 호환하지 않았다.)

 

하지만 정확한 디렉토리 구분자를 쓰는 것을 권장한다.

많은 소프트웨어가  가지 모두를 호환하지만 그렇지 않는 프로그램도 많기 때문이다. 상황에 맞게 경로를 입력하는 것이 바람직하다.

 

주의 - 보안적 이슈

시스템 API 또한 슬래시와 백슬래시를 모두 지원하는 경우가 있다. 예를 들어 win32 api의 CopyFile 함수의 파라메터에 슬래시가 포함 된 경로를 입력할 경우 백슬래시로 변경하여 인식하게 된다. 이 때문에 사용자로부터 경로를 입력받아 처리하는 경우, 입력 값에 슬래시가 포함되어 있는 상황을 고려해야 한다. 

예를 들어 사용자로부터 입력받은 경로가 C:\Windows\System32  경우 에러가 발생하도록 프로그램을 개발한다고 가정해보자.

입력받은 파라메터를 “C:\Windows\System32” 단순비교 한다면, 사용자가 경로를 “C:/Windows/System32” 입력 할 경우 이를 찾아내지 못할 것이다. 하지만 시스템 API “C:/Windows/System32”에도 정상 동작하므로 프로그램은 해당 경로로 접근할 것이다.

그렇기에 경로를 입력받을  디렉토리 구분자를 통일시키는 작업을 선행하는 것이 바람직하다.

 

[참고]

https://www.howtogeek.com/181774/why-windows-uses-backslashes-and-everything-else-uses-forward-slashes/

3개의 노드로 구성된 쿠버네티스 클러스터에서 replica가 2인 pod를 배포하고 NodePort로 서비스를 등록했더니 접근했던 노드에서 실행 중인 pod만 응답을 하는 현상이 발생했다.

 

정상적인 상황이라면 어떤 노드로 접근을 하던지 서비스는 실행중인 pod 중 하나로 연결을 해줘야 하는데, 접근을 시도했던 IP의 노드에서 동작중인 pod에만 접근이 가능하였다. 3개 노드 중 pod가 떠있지 않은 노드로 연결을 시도하면 pod에 연결되지 못해 응답을 받지 못했다.

 

이를 해결하기 위해 두 가지 설정을 체크해야 한다.

 

1. Forward 방화벽 설정

노드로 접근한 트래픽을 다른 노드로 Forward 하기 위해 노드들에서 방화벽을 허용해야 한다.

# iptables -P FORWARD ACCEPT

 

2. CNI CIDR 설정

나의 경우에는 CNI로 Calico를 사용했다. Calico 설치 시 pod의 네트워크 CIDR를 설정하는데 이 값을 kubernetes의 CIDR와 매칭 시켜야 한다.

...
  - name: CALICO_IPV4POOL_CIDR
    value: 10.5.0.0/16
....

 

Known DLL이란 Windows 운영체제가 아주 특별하게 취급하는 몇몇의 DLL을 말한다.

 

Known DLL에 속한 DLL은 시스템 부팅 시 미리 캐시로 로드가 되고, 이후 해당 DLL을 호출할 때 시스템에서 DLL들을 검색하지 않고 미리 캐시 된 DLL을 사용하게 된다.

Known DLL 목록은 아래 레지스트리를 통해 확인이 가능하다.

레지스트리: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\KnownDLLs

레지스트리를 보면 아래와 같이 DLL 정보가 입력되어 있는 것을 확인할 수 있다.

Known-dll 정보

 

애플리케이션이 LoadLibrary나 LoadLibraryEx 함수로 DLL을 호출할 때, DLL 이름에 .dll 확장자가 포함되어 있으면 확장자를 제거한 이름을 위의 레지스트리에서 찾아 그 값에 입력되어 있는 DLL을 호출한다.

만약 DLL 이름에 .dll 확장자를 넣지 않으면 일반적인 DLL 검색 순서에 따라 검색을 진행한다.

 

원래는 애플리케이션의 로딩 속도 개선이 목적이었으나 보안적인 측면에서도 의미가 있다.

애플리케이션이 DLL 검색 순서에 따라 DLL을 찾을 때, 공격자가 우선순위가 높은 곳에 악성 DLL을 심어 놓으면 애프리케이션은 악성 DLL을 로드하게 되고 애플리케이션의 권한을 탈취할 수 있게 된다.

하지만 Known DLL을 사용할 때에는 시스템에서 DLL을 검색하지 않고 캐시 된 DLL을 사용하므로 이러한 공격을 회피하는데 도움을 준다.

Windows 프로그래밍에서 DLL 호출 시 전체 경로를 입력하여 호출할 수도 있지만 모듈명 만으로도 호출이 가능하다. 전체 경로를 명시하지 않고 DLL을 동적으로 호출할 경우 시스템은 DLL 검색 순서에 따라 DLL을 찾는다.(DLL 검색순서 포스트 참조)

 

그런데 모듈명 만으로 DLL을 호출하게 될 경우 보안적 이슈가 발생할 수 있다.

만약 공격자가 DLL 검색 경로 중 하나의 권한을 취득할 경우 DLL 검색 경로에 악성코드가 존재하게 될 수 있다. 이를 DLL Preloading 공격 또는 Binary Planting 공격이라 부른다. 시스템이 손상당한(혹은 공격당한) 디렉토리를 검색하기 전에 적당한 DLL을 찾지 못할 경우 애플리케이션은 악성 DLL을 로드하게 된다. 애플리케이션이 administrator 권한으로 동작하게 될 경우 공격자는 로컬 권한 상승을 할 수 있다.

 

이러한 공격을 방지하기 위해 아래와 같은 방법으로 개발할 수 있다.

 

  • LoadLibrary, LoadLibraryEx, CreateProcess, ShellExecute 함수를 호출할 때 전체 경로를 명시한다.(가장 바람직하다)

  • LoadLibraryEx 함수 호출 시 LOAD_LIBRARY_SEARCH 플래그를 사용한다. 또는 SetDefaultDllDirectories 함수에 이 플래그를 사용하여 DLL 검색 순서를 다시 수립한다. AddDllDirectory 또는 SetDllDirectory 함수로 리스트를 수정할 수 있다.

    ※ Windows 7, Windows Server 2008 R2, Windows Vista, Windows Server 2008은 이러한 플래그와 함수를 사용하기 위해 KB2533623을 설치해야 한다.

    ※ SetDllDirectory는 프로세스 초기화 시 한 번만 호출한다. 프로세스 전체에 영향을 미치므로 멀티 스레드 환경에서 다른 값을 설정할 경우 정의되지 않은 동작을 할 수 있다. 특히 third-party DLL을 사용할 경우 주의해서 사용해야 한다.

  • 애플리케이션이 정확한 DLL을 호출하는 것을 보장하기 위해 DLL 리다이렉션이나 manifest를 고려한다.

  • 표준 검색 순서를 사용해야 할 경우 Safe DLL 검색모드가 활성화 되어 있도록 보장해라. 이는 사용자의 현재 디렉토리의 검색 우선순위를 낮추어 시스템이 악성 DLL보다 올바른 DLL을 먼저 찾을 수 있도록 돕는다.

  • Safe Process 검색모드가 활성화 되어 있지 않은 경우 LoadLibrary 함수에서 사용할 경로를 얻기 위해 SearchPath 함수를 사용하지 마라. Safe Process 검색모드가 활성화 되어 있지 않으면 LoadLibarary와 다른 검색 순서를 사용하게 된다. Safe Process 모드는 SetSearchPathMode로 설정 가능하다.

 

[참고문서]

https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-security

Windows에는 동일한 이름의 DLL도 여러 버전이 존재할 수 있다. 애플리케이션은 DLL의 전체 경로를 명시하거나 리다이렉션을 이용하거나 manifest를 이용하여 어느 위치에 있는 DLL을 사용할 것인지를 정할 수 있다. 만약 이러한 방법 중 어느 방법도 취하지 않는다면, 시스템은 미리 약속 된 순서로 DLL을 검색한다. 이 포스트에서는 시스템이 DLL을 검색하는 미리 약속 된 순서를 설명한다.

 

DLL 검색에 영향을 주는 요소들

- 만약 메모리에 동일한 이름의 DLL이 로드되어 있을 경우 시스템은 리다이렉션과 manifest만 확인하고 DLL을 검색하지 않는다.

- 만약 애플리케이션이 실행중인 윈도우의 Known-DLL 목록에 포함되어 있는 DLL을 로드할 경우 시스템은 DLL을 검색하지 않고 Known-DLL의 복사본을 사용한다. 현재 시스템의 Known-DLL 목록은 다음 레지스트리 경로에서 확인할 수 있다.

Known-DLL list: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

 

데스크탑 애플리케이션의 표준 검색 순서

시스템의 DLL 검색 순서는 Safe DLL 검색모드의 활성 여부에 따라 달라진다. Safe DLL 검색모드는 사용자의 현재 디렉토리의 검색 순서를 뒤쪽으로 변경한다.

Safe DLL 검색모드는 default로 활성화 되어 있다. 이를 비활성화 하기 위해서는 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 레지스트리의 값을 0으로 설정한다. SetDllDirectory 함수로도 검색 경로에 특정 디렉토리가 있는 동안 Safe DLL 검색모드를 비활성화 할 수 있다.

 

※ Windows XP는 Safe DLL 검색모드가 default로 비활성화 되어 있다. 이를 활성화 하기 위해서는 레지스트리를 1로 변경해야 한다.

Windows XP Service Pack 2부터 Safe DLL 검색모드가 default로 활성화 된다.

 

 

만약 Safe DLL 검색모드가 활성화되면 아래 순서로 검색을 한다.

1. 애플리케이션이 로드 된 경로

2. 시스템 디렉토리(GetSystemDirectory 함수로 얻은 경로)

3. 16-bit 시스템 디렉토리(함수로는 경로를 얻을 수 없지만 검색순서에 포함 됨)

4. Windows 경로(GetWindowsDirectory 함수로 얻은 경로)

5. 현재 경로

6. 환경변수 PATH에 존재하는 경로

 

Safe DLL 검색모드가 비활성화되면 아래 순서로 검색을 한다.

1. 애플리케이션이 로드 된 경로

2. 현재 경로

3. 시스템 디렉토리(GetSystemDirectory 함수로 얻은 경로)

4. 16-bit 시스템 디렉토리(함수로는 경로를 얻을 수 없지만 검색순서에 포함 됨)

5. Windows 경로(GetWindowsDirectory 함수로 얻은 경로)

6. 환경변수 PATH에 존재하는 경로

 

※ 중요
공격자가 검색 경로 중 하나의 권한을 취득할 경우 보안 위협이 될 수 있다.
DLL 보안 포스트를 참조하여 이러한 위협을 방지한다.

 

기타 참고사항

- Windows Store App의 경우 DLL 검색 순서가 다르다. 아래 참고 문서를 참조한다.

 

[참고 문서]

https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order

리눅스에서는 base64 명령으로 base64 인코딩, 디코딩을 쉽게 할 수 있다.

 

1.  문자열 인코딩, 디코딩

1

2

3

4

5

6

7

// base64 encoding (with new_line)

# echo 'hello world' | base64

aGVsbG8gd29ybGQK

 

// base64 decoding

# echo 'aGVsbG8gd29ybGQK' | base64 --decode

hello world

 

이 때 주의할 점은 개행(\n)이 함께 인코딩 된다는 점이다.

개행을 제외하여 인코딩을 하고자 할 경우에는 -n 옵션을 추가하여 인코딩을 한다.

1

2

3

4

5

6

7

// base64 encoding (without new_line)

# echo 'hello world' | base64

aGVsbG8gd29ybGQ=

 

// base64 decoding

# echo 'aGVsbG8gd29ybGQ=' | base64 --decode

hello world#

 

2. 파일 인코딩, 디코딩

파일을 이용하여 인코딩, 디코딩을 할 수 있다.

1

2

3

4

5

6

7

// base64 encoding

# echo 'hello world' | base64 > hello.txt

# cat hello.txt

aGVsbG8gd29ybGQK

// base64 decoding

# base64 -di hello.txt

hello world

FQDN은 '절대 도메인 네임' 또는 '전체 도메인 네임' 이라고도 불리는

도메인 전체 이름을 표기하는 방식을 의미한다.

 

웹 사이트 주소를 예로 들어보자.

 

1. www.tistory.com  

2. onlywis.tistory.com

 

위의 두 주소 중 www  onlywis 부분이 '호스트'이고, tistory.com 부분이 '도메인'이다.

위에 쓴 것처럼 호스트와 도메인을 함께 명시하여 전체 경로를 모두 표기하는 것을 FQDN 이라 한다.

 

FQDN와 달리 전체 경로명이 아닌 하위 일부 경로만으로 식별 가능하게 하는 이름을 PQDN(Partially~)라 한다.

 

쿠버네티스의 경우 다른 Pod를 찾을 시 동일 네임스페이스 안에서 찾을 때에는 PQDN을 사용할 수 있지만,

네임스페이스 외부에서 찾을 때에는 FQDN을 사용해야 한다.

예전에는 수 개월(혹은 수 년)에 한 번씩 서비스를 릴리즈 했었지만, 최근에는 서비스를 더 작게 만들고(마이크로서비스) 더 자주 배포(Deployment) 하는 방식으로 변화하고 있다. 이러한 트렌드에 맞춰 서비스 배포 전략도 다양하게 개발되고 발전되어 왔다.


본 포스트에서는 가장 대표적인 배포 전략 Rolling, Blue/Green, Canary 기법에 대해 알아보겠다.



1. Rolling

Rolling 배포는 서버를 한 대씩 구 버전에서 새 버전으로 교체해가는 전략이다. 서비스 중인 서버 한 대를 제외시키고 그 자리에 새 버전의 서버를 추가한다. 이렇게 구 버전에서 새 버전으로 트래픽을 점진적으로 전환한다. 이와 같은 방식은 서버 수의 제약이 있을 경우 유용하나 배포 중 인스턴스의 수가 감소 되므로 서버 처리 용량을 미리 고려해야 한다.




2. Blue/Green

Blue/Green 배포는 구 버전에서 새 버전으로 일제히 전환하는 전략이다. 구 버전의 서버와 새 버전의 서버들을 동시에 나란히 구성하고 배포 시점이 되면 트래픽을 일제히 전환시킨다. 하나의 버전만 프로덕션 되므로 버전 관리 문제를 방지할 수 있고, 또한 빠른 롤백이 가능하다. 또 다른 장점으로 운영 환경에 영향을 주지 않고 실제 서비스 환경으로 새 버전 테스트가 가능하다. 예를 들어 구 버전과 새 버전을 모두 구성하고 포트를 다르게 주거나 내부 트래픽일 경우 새 버전으로 접근하도록 설정하여 테스트를 진행해 볼 수 있다. 단, 시스템 자원이 두 배로 필요하고, 전체 플랫폼에 대한 테스트가 진행 되어야 한다.




3. Canary

'Canary'라는 용어의 어원을 알면 이해가 더 쉽다. Canary는 카나리아 라는 새를 일컫는 말인데, 이 새는 일산화탄소 및 유독가스에 매우 민감하다고 한다. 그래서 과거 광부들이 이 새를 옆에 두고 광산에서 일을 하다가 카나리아가 갑자기 죽게 되면 대피를 했다고 한다.

Canary 배포는 카나리아 새처럼 위험을 빠르게 감지할 수 있는 배포 기법이다. 구 버전의 서버와 새 버전의 서버들을 구성하고 일부 트래픽을 새 버전으로 분산하여 오류 여부를 판단한다. 이 기법으로 A/B 테스트도 가능한데, 오류율 및 성능 모니터링에 유용하다. 트래픽을 분산시킬 라우팅은 랜덤으로 할 수도 있고 사용자 프로필 등을 기반으로 분류할 수도 있다. 분산 후 결과에 따라 새 버전이 운영 환경을 대체할 수도 있고, 다시 구 버전으로 돌아가 수도 있다.





[참고]

https://www.slideshare.net/awskorea/ci-cd-pipleine-on-aws

https://dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3

'IT기술 > DevOps' 카테고리의 다른 글

CI/CD란 무엇인가?  (0) 2019.03.08

+ Recent posts