우분투 리눅스 10.04.4 LTS에서 버퍼오퍼플로 취약점 점검
이번 섹션을 마지막으로 버퍼오버플로우 버그를 일으키는 실습을 정리하면서 어떻게 레지스터와 스택이라는 메모리 공간을 통해 공격자의 코드를 실행시킬 수 있는지 확인해보겠다. 화이트 해커가 공격을 할 수 있는 것은 그러한 공격을 단순히 따라 하는데 그치지 않고 같은 원리의 다른 공격을 유추할 수 있는 능력까지 키우는 게 목적이라는 게 본인의 생각이다.
예상할 수 있는 공격뿐만 아니라 예상치 못한 공격에 대한 대응을 할 수 있는 것이 보안이 공격을 앞설 수 있는 유일한 방법이다.
앞선 섹션에서 디버그 단에서 메모리 상태를 확인하는 명령을 치면 아래와 같은 화면이 나온다(명령어 x/16xw $rsp).
32비트로 치면 ESP(Extended Stack Pointer)라는 메모리 끝단인 0x7ffffffffe2c0부터 0x7fffffffe2f0까지 4개의 레지스터 레이블에 해당하는 메모리 데이터를 보여준다. 앞서서 run 'AAAA' 명령어를 입력한 후 이미 설정되어 있는 첫 번째 중단점까지의 메모리 내용이다. 첫 번째 중단점은 main() 함수 내부의 function() 함수를 호출하기 전의 행인 15행이었고 이 중단점까지의 메모리 내용은 곧 main() 함수가 사용한 스택 메모리 번지값들이다.
이번에는 명령어 'x/1xw $rbp' 명령어로 다음과 같은 메모리 내용을 확인해보자.
위의 결과를 통해 %ESP 레지스터에 해당하는 메모리 데이터는 아래 네 개의 번지와 같다.
한번 더 continue 명령을 입력해서 다음 중단점(10행)인 function() 함수가 호출되어 함수 내부의 strcpy 함수가 실행되기 직전까지의 메모리 데이터를 출력한다. 그래서 다시 'x/16xw $rsp' 명령과 'x/1xw $rbp' 명령으로 %ESP와 %EBP의 레지스터를 출력해서 다음 function() 함수 부분의 메모리 데이터를 확인해본다.
function() 함수의 %ESP 레지스터 구간은 위의 출력 화면과 같으며 그 위의 화면에서 'disass(embly) main' 명령을 통해 main() 함수의 어셈블리 과정을 확인할 수 있다. 여기서 알아야 할 포인트가 function() 함수(화면에서 <function1> )를 호출하는 지점에서 다음 main() 함수로 jump 해야 할 주소(RETurn addr.)가 '0x00...04005b7'이라는 것이다.
스택 메모리의 논리적 흐름을 앞선 섹션들에서 말했듯이 caller 함수인 main() 함수에서 function() 함수를 호출하면 function() 함수 내부의 루틴을 실행하기 전에, 먼저 루틴 수행 후 복귀해야 할 주소(RETurn addr.)를 %edi(extended destination index: 목적지 인덱스) 레지스터로 push 한다. 그래서 'callq 0x400574 <function1>'이라는 어셈블리 호출 명령 다음에 바로 복귀 주소인 메모리 번지가 나와야 하고 그 번지를 로딩하는 레지스터는 %edi다.
한 번 더 continue 명령을 입력하면 다음번 중단점(11행)까지 실행되면서 strcpy 함수가 실행되고 난 이후의 function() 함수의 바뀐 메모리 데이터를 확인할 수 있다. 위의 그림에서 '0x004005b7' 메모리 데이터의 복귀 주소가 그대로 있다는 것을 알 수 있고, '0x41414141' 부분(본래 0xf7a69aa8 메모리 데이터 '주소값'에 덮어씀)이 문자열 'AAAA'가 메모리에 기록된 부분이라는 것도 판단할 수 있다. A는 16진수로 ‘41’이므로 바뀐 부분을 눈으로 찾으며 되고, 뒤따라나오는 본래의 '0x00007fff'이었던 메모리 부분이 '0x00007f00'으로 바뀐 것으로 보아 'AAAA' 글자 뒤에 공백 1 byte(개행문자 : Carrige Return Line Feed)가 뒤따라와서 ‘00’으로 바뀌었다는 것도 확인할 수 있다.
이제 gdb 명령 단에서 처음과 달리 다음과 같은 명령으로 시작해본다.
run $(perl -e 'print "A" x 30')이라는 명령은 펄(perl)이라는 스크립트 언어를 이용해서 실행하는 구문으로 A라는 문자를 30번 출력해라는 명령이다. 이후에 continue 명령을 계속 입력하면서 명령어 실행 후 중간에 끊기지 않고 계속 실행할 수 있게 하기 위해 펄을 이용한다.
다음 continue 명령을 통해 function() 함수의 메모리 구조까지 다시 살펴보고 앞서 복귀 주소로 확인한 부분을 위의 출력 화면을 통해 다시 확인할 수 있다(RET으로 표시한 부분).
이제 흥미로운 부분이 나온다. 한번 더 continue 명령을 입력하면 이전과 달리 ‘41’로 시작하는 메모리 데이터 부분이 훨씬 더 많이 발생한 것을 확인할 수 있다. 정확히 '41'이 30개 더 메모리 데이터들을 덮어썼다는 것을 알 수 있다. 이제 공격을 위한 소스 코드의 주소를 몇 번째 이후에 입력해야 할지 정확히 계산해야 할 시점이다.
Section 7-3: RCE testing 에서 이어짐.