복습
ASLR
주소공간 레이아웃 랜덤화
켤 때마다 메모리 주소가 바뀌므로 이를 이용한 공격이 불가함
스택 움직임(from - 6장 리버싱 기초)
버퍼 오버플로우
경계 검사 소홀 등으로 의도한 영역을 넘어선 영역에 대한 읽기/쓰기가 발생할 수 있는 취약점
공격자가 원하는 코드를 실행시키는 등 악용될 여지가 있다.
스택 버퍼 오버플로우
스택 영역의 버퍼에 크기를 초과하는 데이터를 쑤셔넣어 리턴 주소까지 변경해버리는 공격
스택 프레임의 최하단에는 ret(리턴 주소)가 들어가있으므로 영향이 직접적이다.
왜 리턴주소가 들어가있죠?
이거 까먹었으면 모 교수 말대로 수업 드랍해야한다.
call 인스트럭션은 push %eip + jmp $addr 이다.
이후 함수의 프롤로그가 실행 — push %ebp, mov %esp, %ebp —된다.
따라서 함수 body의 첫 인스트럭션을 실행할 때, ebp에는 기존 ebp 주소(복구할 ebp 주소)가, ebp +4에는 기존 EIP 주소, 즉 리턴 주소가 들어있다.
따라서 취약한 함수(경계 검사가 소홀한 함수)를 대상으로 임의 데이터를 넣어 이 리턴 주소를 원하는 주소로 변경하는 공격을 할 수 있다.
실습
아래 경우는 strcpy 대상 스택 영역보다 아래쪽에 있는 RET 주소가 AAAA로 변했을 것이다.
Hex로는 0x41 0x41 0x41 0x41.
확인해보자.
교재 환경과 조금 달라서 리턴 주소가 좀 다르다.
그러나 상황을 분석해보면, strcpy 인자로서 스택에 2개를 쌓았으며, 그 아래에는 지역변수 buffer, 그 아래에는 함수 프롤로그에서 저장된 기존 ebp, 그 아래에는 함수 호출 시 저장된 리턴 주소가 있는 상황이다.
여기서 strcpy가 실제로 실행되면, 위 그림처럼 buffer를 넘어 기존 ebp 및 리턴 주소까지 건드릴 수 있게 된다.
exploiting
그럼 이런 현상을 활용해 쉘 코드를 실행하게 해보자.
스택에 쉘 코드를 넣고, NOP Sledding을 활용하면 대충 그 주소 어디쯤으로 떨궜을 때 쉘 코드가 실행된다.
‘대충 그 주소’는 어떻게 추측할 수 있을까?
aslr를 껐으니까 임의의 프로그램과 해당 프로그램의 스택 영역 주소가 같을 것임을 알 수 있다.
쉘 코드는 인자(argv)로 들어갈 것이다.
따라서 해당 영역에 충분한 NOP를 깔고, ‘임의의 ESP값에서 지역변수 할당 등을 감안하고도 argv 영역에 닿을 수 있을 만큼 충분히 큰 값’ 만큼 점프를 해서 NOP에 도달하면 된다.
한번 해보자.
정확한 이유는 모르겠지만 한참 안되다가 갑자기 된다;
NOP 영역으로 점프되었기 때문에 코드가 실행된 것.
정리하면
- 쉘코드 및 NOP Sledding을 프로그램 인자로 넣었음
- strcpy 의 취약점(경계검사X)을 사용해 함수의 리턴 주소를 위 주소 언저리로 덮어씌웠음
중요한 점은 구조상 리턴 주소가 어디있을지 예측하는 것, 그리고 그걸 어떻게 덮어씌울지 고민하는 것이다.
이걸 보고 “음~ 스택을 8바이트 할당하는군~” 을 알면되고,
“그럼 8바이트 밑(스택이니까 위로 쌓인다)에는 기존 ebp가 4바이트, 그보다 밑에는 함수 리턴 주소 4바이트가 있겠군”
“경계 검사가 없으니까 8바이트 + 4바이트로 ebp까지 밀어버리고, 리턴 주소를 덮어씌우면 되겠구만”
“쉘코드는 NOP 열심히 깔고 프로그램 인자로 대충 집어넣지 뭐”
의문점
- 왜 복사하지? 굳이 strcpy로 복사하지 않아도 인자로 들어갈 때부터 이미 쉘코드가 삽입된 것 아닌가?
맞다. 맞는데 틀렸다. 쉘 코드는 복사한 적이 없다. 다만 인자[1]을 버퍼로 복사했고, 그 과정에서 취약점에 의해 리턴 주소까지 덮어 씌워졌을 뿐이다.
힙 버퍼 오버플로우
힙이라고 예외가 아니다.
얘도 메모리에 연속적으로 할당되는 경우가 있다. (malloc은 기본적으로 여유를 두고 만들어지므로 예외)
이런 프로그램이 있다.
뭐 당연히 예상한대로 실행하면 func의 인자를 뱉는다.
그리고 메모리 구조를 분석해보면 buf 와 funcptr은 heap 영역에 연속적으로 할당되어있음을 알 수 있다.
그러면 strncpy가 경계검사가 소홀한 점(사실상 없다)을 활용해 얘도 funcptr이 가리키는 주소를 덮어씌울 수 있다.
buf에 64바이트 더미를 넣고 4바이트 주소를 추가로 넣으면 그렇게 될 것이다.
이걸 방어하려면 어떻게 했어야 할까?
다른 보호 기법들을 적용하지 않았더라도 strlen() 대신 sizeof()로 고정된 값을 사용했어야 맞다.