The injection of the buffer overflow #1
스택 단에서 버퍼오버플로우 공격의 원리를 알아보기 위해 아래의 사이트에서 우분투 리눅스 이미지 파일(iso)을 다운로드한 후 Vmware를 통해 로딩시키면 된다(http://old-releases.ubuntu.com/releases/).
실제 공격의 원리를 따라가다 보면 어셈블리어(Assembly Language: 저수준 언어로 사용자가 알아볼 수 있도록 네이티브 코드에 기능을 나타낸 라인에 별명(영어)을 붙이는 방법)에 따라 레지스트리가 어떻게 동작하는지 알아야 한다. 그리고 오버플로우를 행했을 때의 메모리 스택 단은 아래 '그림 1'과 같다. 함수의 스택 프레임 단의 'buffer'라는 변수의 주어진 할당량(5 byte)을 넘어서 상위의 RETurn 주소 영역과 main 함수의 스택 프레임까지 'A'라는 문자가 침범한 것을 확인할 수 있다. 리눅스 시스템에서 이 오류가 벌어지면 'Segmentation Fault'라는 메시지가 뜨는데 이 지점에서 프로그램은 회복 불가능하다(조지아 와이드먼, 2015).
어셈블리어 언어로 프로그래머가 직접 작성하지 않고 C나 Python 등의 고급언어로 간단한 함수(Caller와 Callee) 코드를 만든 후, 이 소스 코드를 어셈블리어 소스 코드로 변환할 수 있다. 이 기능을 활용해서 C로 작성한 소스 코드와 어셈블리어 소스 코드를 비교할 수 있다(Hisao Yazawa, 2003). 그리고 해커들이 유포한 악성코드를 분석하기 위해 인코딩(암호화)된 네이티브 코드를 디코딩하여 이 네이티브 코드(컴퓨터가 보고 실행시키는 기계어)를 악성코드 분석가가 해석하기 위해 어셈블리어 소스 코드로 변환하는 것을 '역어셈블' 혹은 '리버스 엔지니어링', 줄여서 '리버싱'이라고도 한다.
왜 기존의 C나 Python으로 작성한 소스 코드가 컴파일된 후 생성된 네이티브 코드를 역컴파일 해서 사용자가 알아보기 더 쉽도록 바로 소스 코드를 생성하지 않고 어셈블리어라는 의사 코드 형식(Pseudo code)으로 역변환할까? 왜냐하면 고급언어로 작성한 소스코드는 네이티브 코드와 1:1로 대응되지 않을뿐더러 반드시 원래의 소스 코드가 생성된다는 보장이 없기 때문이다. 즉 역어셈블(리버싱)에 비해 무척 어렵기 때문이다. 그래서 악성코드 분석가들은 '리버스 엔지니어링'이라는 보안 업무에서 상위 레벨에 해당하는 작업을 수행한다. 물론 암호화된 소스 코드를 해제시키는 작업인 디코딩(복호화, decoding) 이후에야 가능한 작업이다.
본인은 국민학교 저학년 시절 때, 컴퓨터 학원에서 서열이 한 두세 번째 정도로 오래 다닌 수강생이었다. 그때 서열 첫 번째로 되어 보이던 내 또래 여자 아이는 'Fortran'이라는 과학용 업무를 위한 언어를 배우고 있었다. 그리고 중학생으로 보이던 한 형은 코볼인가 '어셈블리어'라는 책을 들고 다녔던 걸로 기억하는데, 그 형은 컴퓨터학원이 속셈학원으로 확장되면서 당시 천재교육에서 출판하는 수학 문제집도 자주 꺼냈다. 그리고 거기의 문제 한 꼭지를 수학 선생님한테 끈질기게 물으며 쫓아다니던 전형적인 '찐따'였다. 그 형이 지금 무엇을 하고 있을지 모르지만 그 당시 'Lotus 1-2-3'나 'Database+' 같은 'MS-DOS' 단에서 작동하는 사무용 애플리케이션 명령어나 깔짝거리고 있었던 나에 비하면 분명히 IT 분야에서 이미 제 갈 길을 구축하고 있을 거라고 짐작한다.
어쨌든 어셈블리어는 사실 C나 BASIC보다 더 이해하기 쉽다고 해도 과언이 아니지만 시스템의 레지스터가 어떻게 동작하는지 구체적인 명령어(OPerand code)와 목적어(Operand)를 나타내 주고 있기 때문에 CPU에 대한 하나의 명령어를 '한 줄'씩 표현한 컴퓨터 단의 언어(네이티브 언어 + 니모닉*언어)라고 할 수 있다(Hisao Yazawa, 2003). 그러면 아래의 C언어로 작성한 간단한 오버플로우 발생 코드인 함수(overflowed)와 호출되는 코드인 function 함수 그리고 main(프로그램 실행 시 처음으로 시작하는 엔트리 포인트) 함수로 구성된 소스 코드를 보자.
이 C 프로그램은 세 개의 함수 overflowed, function, main이 등장한다. overflowed가 호출되면 콘솔에 "실행이 가로채였음(Execution Hijacked)"이라는 문자가 출력된 다음 리턴한다. function이 호출되면 지역 변수, 다섯 개의 문자를 담는 buffer라는 문자열을 선언한다. strcpy 명령어는 function으로 전달된 변수의 내용을 buffer로 복사한다. 프로그램이 시작될 때 기본적으로 호출되는 main 함수는 function을 부른 다음 프로그램이 수신한 첫 번째 커맨드 라인인 인자(agrv [1])를 전달한다. function이 리턴하면 main은 콘솔에 "실행이 정상적으로 이루어졌음(Executed normally)"이라고 출력한 다음 프로그램이 끝난다(조지아 와이드먼, 2015)**.
정상적인 상황에서는 overflowed가 절대 호출되지 않기 때문에 "Execution Hijacked"라는 문자열을 콘솔에서 볼 수 없다. (이후 프로그램 속에 이런 코드를 삽입하여 버퍼를 넘치게 하여 프로그램의 제어권을 가져오는 일련의 상황을 참조한 책을 보고 따라 해 보시기를 바란다. 어떤 원리를 통해 공격이 이루어지는 지에 대한 이해만 수반된다면 실제 공격은 그 원리에 따라 여러 번의 시행착오를 통해 시도할 수 있으므로 이후 우분투의 디버거인 GDB를 이용한 중단점(break point) 설정의 내용은 마지막(섹션 6) 섹션에서 다루겠다.
다음 섹션(섹션 4)에서는 참조 서적에서 다룬 '스택 기반에서의 버퍼오버플로우 공격'의 개념을 마저 다루겠다.
해킹 과정의 요소요소를 통한 시스템 개념의 이해가 컴퓨터에 대한 이론을 무지상태에서 답습하는 것보다 훨씬 효과적인 학습 과정이 이루어진다. 이론이라는 실체도 실제 시스템의 구현원리를 분석해서 하나의 정지된 글로 누적한 것이다. 실제로 학계에서 증명된 이론을 '스택'이라고도 부른다. 처음부터 이론으로 시작해서 이론으로 증명하는 이론 물리학 등의 순수 학문을 제외하고는 세상의 모든 이론은 움직이는 세상의 원리 중 어느 한 현상을 포착해서 정지된 화면에 대해 학자가 분석해서 대중이 진리라고 공감할 수 있도록 표현한 매개물이다. 하지만 학습의 시작이 그렇게 분석된 매개물만을 통해 실제 세상의 원리를 재생시켜보고자 한다면 곧 머릿속에서 버퍼링이 일어나면서 한계에 부딪힐 것이다. 왜냐하면 학자가 이론을 분석한 순서는 실체에서 이론 순이었는데, 단지 누군가 전체 시스템 중 일부에 불과한 한 이론을 통해 뭔가를 깨우치고자 한다면 그것은 말 그대로 탁상공론이나 다름없기 때문이다.
그래서 '시스템적 사고'로 먼저 실제로 전체 프로세스가 어떻게 진행하는 건지 H/W의 핵심인 CPU(마이크로 프로세서)의 프로그램 진행을 담당하는 OS(운영체제)의 흐름을 꿰뚫고 있다면 컴퓨터 전체에서 어디가 취약하므로(버퍼오버플로우는 메인 메모리) 여기를 유심히 살펴봐야 한다는 '문제 발견 능력'이 길러질 수 있다. 해킹이든 개발이든 컴퓨터 이론(즉 시스템적인 사고)의 기본기가 중요하다는 말은 세상을 사는 데 필요한 기본기(상식, 체력, 도리)가 중요하다는 말과 같다고 생각한다.
* 니모닉(mnemonic) 언어 : 예를 들어 덧셈을 하는 네이티브 코드에는 add, 비교를 하는 네이티브 코드에는 cmp(compare의 줄임말)와 같은 별명을 붙이는 데, 이런 별명을 니모닉이라고 하며 니모닉을 이용하는 프로그래밍 언어를 '어셈블리어(assembly language)'라고 부름(Hisao Yazawa, 2003).
** 참조한 책은 아래 참조 2) 원서의 번역판에 해당하는데 번역가가 본인이 직접 실습하면서 번역한 게 아니라서 그런지, 출력하는 결과에 대한 문구조차도 아예 다르게 썼다(오역이 아니라 내용이 틀렸다). 그래서 만약 실제로 이 프로그램 소스 코드로 버퍼 오버플로우 개념을 잡겠다고 2) 참조 책을 볼 거라면 번역본이 아닌 이 원서를 참고하시기 바란다.
Reference
1) Hisao Yazawa. 예승철, A. (2002). 성공과 실패를 결정하는 1%의 프로그래밍 원리: How program works, 서울: 성안당. com.
2) Weidman, G. (2014). Penetration testing: A hands-on introduction to hacking. San Francisco: No Starch Press.