CMU(Carnegie Mellon University)에서 개설된 CS:APP(Computer Systems: A Programmer's Perspective) 과목에서 사용되는 두 가지 실습 내용이 있다.
- Bomb Lab (reverse engineering)
- Attack Lab (buffer overflow)
위 내용과 관련한 이론을 실제로 적용하고 학습한 내용을 실습해 보는 시간을 가져본다.
또한 실습과정에서 사용하는 debugging 과정을 이해한다.
■ myecho: target program (binary) with a buffer overflow
- You must make it read and print the content of secret.txt
■ hint.c: partial or full source code of the target program
■ exploit-myecho.py: exploit code that you have to fill in § Don’t edit any other file in the directory
주어진 파일들로 reverse enginerring과 buffer overflow의 개념을 모두 학습한다.
Reverse Engineering
Reverse Engineering은 software나 hardware의 동작 원리를 이해하고, 내부 동작을 파악하기 위해 기술고 ㅏ기법을 사용한다. Source code나 binary file 혹은 다른 실행 파일을 분석하고 역추적하여 내부 구조와 동작을 이해하는 것이다.
hint.c에 myecho 프로그램의 일부 또는 전체 소스코드가 포함되어 있고, 취약점에 대한 힌트를 제공한다. 이러한 소스 코드를 분석해서 프로그램이 어떻게 동작하는지 어떤 취약점이 있는지 등을 알아낸다.
Buffer Overflow
Buffer overflow는 입력받은 데이터를 처리할 때, 할당된 buffer의 크기를 초과하여 데이터를 쓰는 상황이다. 이를 악용해서 프로그램의 흐름을 조작하거나 실행 흐름을 변경할 수 있다. myecho 프로그램은 buffer overflow 취약점을 가지고 있다.
따라서 exploit-myecho.py 파일을 이용해서 myecho의 buffer overflow 취약점을 악용해 secret.txt 파일의 내용을 읽고 출력하는 것을 목표로 한다.
정리하면, myecho 프로그램은 취약한 대상프로그램이다. 프로그램이 입력을 받을 때 할당된 buffer를 초과하여 데이터를 쓰게 되면 buffer overflow가 발생한다. 따라서 hint.c에서 내부 동작과 취약점에 대한 정보를 얻고, source code를 분석하고 이해함으로써 취약점을 파악한다. 이후 exploit-myecho.py에서 악용 코드를 작성하고, myecho 프로그램의 취약점을 이용해 secret.txt 파일의 내용을 읽고 출력하는 코드를 작성해 본다.
우선 GDB를 사용해서 code를 disassemble 한다.
GDB는 debugging을 위한 도구로, 프로그램 실행 중에 코드의 실행을 멈추고 debugging 작업을 수행할 수 있도록 한다. 이러한 과정으로 어셈블리어 코드를 확인하면, 프로그램의 동작을 더 깊이 이해할 수 있고, 버그의 원인을 추적하거나 프로그램의 흐름을 분석하는 데 도움 된다.
gdb -q 명령어를 통해 GDB를 실행하고, 실행 중에 디버그 정보를 제공하는 인터페이스를 확인한다. -q는 "quiet"를 의미하고, GDB를 실행할 때 불필요한 메시지를 출력하지 않도록 한다. 일반적으로 프로그램의 디버그 정보를 읽고 준비하는 동안 로딩 메시지와 같은 많은 출력이 화면에 표시되는데, 이를 생략하고 실행결과만 간결하게 보여준다.
(gdb) prompt가 나타나고, 이후에 GDB의 다양한 debugging 명령어를 입력해서 프로그램의 실행을 제어하고 debugging 작업을 수행한다. 위 실습에서는 disassemble이 목적이므로, disas 명령어를 통해 현재 실행 중인 함수의 어셈블리어 코드를 출력한다.
Command : x/ <N><t> <addr>
'x/'는 GDB의 메모리 검사 명령 중 하나이다. 'x'는 메로리를 검사하는 명령이고, '/'는 출력 형식을 지정하는 구분 기호다. <N>은 출력할 데이터 조각의 개수이고, <t>는 데이터의 타입을 지정한다.
<t>
'b': 1byte
'h': 2byte
'w': 4byte
'g': 8byte
's': String
'xb': 1byte를 16진수로 출력
'xg': 8byte word를 16진수로 출력
즉, x/20xb 0x4007e8은 0x4007e8 주소로부터 20개의 1byte 데이터를 16진수로 출력하라는 의미이고,
x/1s 0x4007e8은 0x4007e8 주소로 부터 시작해서 null-terminated(문자열의 끝에 null문자가 있는 문자열) 문자열을 출력하는 것이다.
sub $0x28, %rsp 명령어를 통해서 stack pointer인 rsp 레지스터의 값을 감소시켜서 stack에 공간을 할당한다. 이후 callq 명령어를 통해서 함수 호출을 하고, 호출된 함수가 완료된 후 어디로 돌아가야 하는지 지정한다. 즉, gets 함수가 호출되기 전에 $rsp+0x28의 값은 echo 함수의 반환 주소인 0x400070e이다. gets 함수가 호출되면 이 return address는 stack에 push 되고, gets 함수가 완료되면, 이 return address는 다시 pop 되어서 echo 함수가 재개된다.
gets() 호출 전후에 break point를 설정해서 stack memory가 buffer overflow로 손상되는 것을 관찰할 수 있다.
Command: b * <addr>
해당 주소에 있는 명령어에 breakpoint를 설정한다. 해당 주소에서 프로그램 실행이 멈추게 된다.
Command: r
프로그램을 실행한다. 설정된 breakpoint에 도달하면 실행이 멈춘다.
Command: c
breakpoint에서 실행을 계속한다. 즉, 멈춘 지점에서 다시 프로그램을 실행시킨다.
우리는 gets() 함수가 호출되기 전에 'xg'를 활용해서 stack memory를 검사하는 것이다.
'xg' <addr> : xg명령어는 gdb에서 메모리를 검사하는 명령어다. 'x'는 'examine'의 약자로 메모리 내용을 확인하는 명령어이고, 'g'는 지정자로 8byte 크기의 값을 표시한다. 따라서 'xg' <addr>은 해당 주소에 있는 8byte word를 16진수로 출력한다.
'<$register>' : <$register>를 <addr> 대신 사용할 수 있다. 이는 레지스터 값이 메모리 주소를 가리킬 때 사용가능하다. 가령 $rsp는 stack pointer register를 나타내므로, $rsp+0x28은 rsp에서 0x28 byte 뒤의 메무리 주소를 가리킨다. $rsp+0x28에는 return address가 들어있음을 알 수 있다.
c Command를 통해서 실행을 시켰고, 그리고 AAAA...BCDE('A' is repeated by 40 times) input을 입력했다. 그 다음 두 번째 breadkpoint에 멈춘 상태이다. 입력 데이터를 너무 많이 제공해서 메모리 버퍼가 이를 수용하기 위한 충분한 공간이 없도록 한다. 이로 인해서 버퍼가 넘쳐흘러 데이터가 의도치않게 메모리의 인접영역으로 흘러 넘치게 되고, 그 결과로 프로그램이 예상치 못한 방식으로 작동한다.
위의 결과에서는 AAAA...BCDE 문자열이 stack에 저장된 return address를 덮어썼음을 보여준다. 특히 $rsp+0x28 위치에 있어야 할 원래의 return address를 더 이상 확인할 수 없고 A문자인 0x41과 BCDE 문자가 있는 것을 확인할 수 있다. 즉, return address가 변질된 것을 아래에서 확인할 수 있다.
이는 buffer overflow 공격의 결과이다. 이후 덮어 쓰여진 return address로 점프하려고 시도했지만, 그 결과로 segmentation fault(잘못된 메모리 접근)가 발생했다. 이는 원래의 return address와는 상관없는 전혀 무효한 주소이기 때문이다.
info reg rip 명령어로 현재 명령어 pointer의 값을 출력한다. 이 값은 프로그램이 다음에 실행할 명령어 주소를 가리키는데, 이는 무효한 주소인 0x45444342를 가리키게 된 것이다.