🔐
땡칠로그/🔐시스템 보안 - Buffer Overflow

시스템 보안 - Buffer Overflow

태그
CS
수업
정리
완료 일시
Jun 11, 2024

복습

ASLR

주소공간 레이아웃 랜덤화
notion image
켤 때마다 메모리 주소가 바뀌므로 이를 이용한 공격이 불가함

스택 움직임(from - 6장 리버싱 기초)

main 진입 직후. 아직 main 함수의 프롤로그가 실행되기 전임.
main 진입 직후. 아직 main 함수의 프롤로그가 실행되기 전임.
main 함수 프롤로그가 실행된 직후. 프롤로그에 의해 esp와 ebp는 같은 곳을 가리키면서 새 스택 프레임이 생성된다.
또한, 새 스택 프레임의 끝에는 이미 기존 ebp가 존재하는 상황임을 알 수 있다.
main 함수 프롤로그가 실행된 직후. 프롤로그에 의해 esp와 ebp는 같은 곳을 가리키면서 새 스택 프레임이 생성된다. 또한, 새 스택 프레임의 끝에는 이미 기존 ebp가 존재하는 상황임을 알 수 있다.
이후 로컬 변수, 인자 등을 사용하면서 자유롭게 프로그램이 실행되고
이후 로컬 변수, 인자 등을 사용하면서 자유롭게 프로그램이 실행되고
에필로그가 실행되면서 esp가 ebp위치로 내려옴 (leave - 1, mov %ebp, %esp)
에필로그가 실행되면서 esp가 ebp위치로 내려옴 (leave - 1, mov %ebp, %esp)
이어서 ebp 복원이 일어나고, esp는 리턴 주소 위치까지 내려온다(pop에 의해). (leave - 2, pop %ebp)
pop %ebp 는 두가지 역할을 한다.
이어서 ebp 복원이 일어나고, esp는 리턴 주소 위치까지 내려온다(pop에 의해). (leave - 2, pop %ebp) pop %ebp 는 두가지 역할을 한다.
마침내 ret을 통해 pop %eip 가 발생하면서, EIP는 기존 함수 위치(리턴 주소)로 복귀되어 실행 상태 복원 및 스택 프레임 복원이 마무리 된다.
인자는 cdecl cdecl 기준 caller가 정리한다.
마침내 ret을 통해 pop %eip 가 발생하면서, EIP는 기존 함수 위치(리턴 주소)로 복귀되어 실행 상태 복원 및 스택 프레임 복원이 마무리 된다. 인자는 cdecl cdecl 기준 caller가 정리한다.

버퍼 오버플로우

경계 검사 소홀 등으로 의도한 영역을 넘어선 영역에 대한 읽기/쓰기가 발생할 수 있는 취약점
공격자가 원하는 코드를 실행시키는 등 악용될 여지가 있다.

스택 버퍼 오버플로우

스택 영역의 버퍼에 크기를 초과하는 데이터를 쑤셔넣어 리턴 주소까지 변경해버리는 공격
스택 프레임의 최하단에는 ret(리턴 주소)가 들어가있으므로 영향이 직접적이다.
💡
왜 리턴주소가 들어가있죠?
이거 까먹었으면 모 교수 말대로 수업 드랍해야한다.
call 인스트럭션은 push %eip + jmp $addr 이다.
이후 함수의 프롤로그가 실행 — push %ebp, mov %esp, %ebp —된다.
따라서 함수 body의 첫 인스트럭션을 실행할 때, ebp에는 기존 ebp 주소(복구할 ebp 주소)가, ebp +4에는 기존 EIP 주소, 즉 리턴 주소가 들어있다.
notion image
따라서 취약한 함수(경계 검사가 소홀한 함수)를 대상으로 임의 데이터를 넣어 이 리턴 주소를 원하는 주소로 변경하는 공격을 할 수 있다.

실습

스택 보호가 켜져있는 상태에서 적용
스택 보호가 켜져있는 상태에서 적용
스택 보호가 꺼져있는 상태에서 적용
스택 보호가 꺼져있는 상태에서 적용
아래 경우는 strcpy 대상 스택 영역보다 아래쪽에 있는 RET 주소가 AAAA로 변했을 것이다.
Hex로는 0x41 0x41 0x41 0x41.
확인해보자.
의도된 영역 8바이트를 넘어서 쓰였으며, 마침내 __libc_start_main으로 돌아가는 리턴 주소까지 건드려버렸다.
의도된 영역 8바이트를 넘어서 쓰였으며, 마침내 __libc_start_main으로 돌아가는 리턴 주소까지 건드려버렸다.
해당 상황을 나타낸 그림이다
해당 상황을 나타낸 그림이다
교재 환경과 조금 달라서 리턴 주소가 좀 다르다.
그러나 상황을 분석해보면, strcpy 인자로서 스택에 2개를 쌓았으며, 그 아래에는 지역변수 buffer, 그 아래에는 함수 프롤로그에서 저장된 기존 ebp, 그 아래에는 함수 호출 시 저장된 리턴 주소가 있는 상황이다.
여기서 strcpy가 실제로 실행되면, 위 그림처럼 buffer를 넘어 기존 ebp 및 리턴 주소까지 건드릴 수 있게 된다.

exploiting

그럼 이런 현상을 활용해 쉘 코드를 실행하게 해보자.
스택에 쉘 코드를 넣고, NOP Sledding을 활용하면 대충 그 주소 어디쯤으로 떨궜을 때 쉘 코드가 실행된다.
‘대충 그 주소’는 어떻게 추측할 수 있을까?
aslr를 껐으니까 임의의 프로그램과 해당 프로그램의 스택 영역 주소가 같을 것임을 알 수 있다.
쉘 코드는 인자(argv)로 들어갈 것이다.
따라서 해당 영역에 충분한 NOP를 깔고, ‘임의의 ESP값에서 지역변수 할당 등을 감안하고도 argv 영역에 닿을 수 있을 만큼 충분히 큰 값’ 만큼 점프를 해서 NOP에 도달하면 된다.
한번 해보자.
notion image
정확한 이유는 모르겠지만 한참 안되다가 갑자기 된다;
NOP 영역으로 점프되었기 때문에 코드가 실행된 것.
정리하면
  1. 쉘코드 및 NOP Sledding을 프로그램 인자로 넣었음
  1. strcpy 의 취약점(경계검사X)을 사용해 함수의 리턴 주소를 위 주소 언저리로 덮어씌웠음
 
중요한 점은 구조상 리턴 주소가 어디있을지 예측하는 것, 그리고 그걸 어떻게 덮어씌울지 고민하는 것이다.
notion image
이걸 보고 “음~ 스택을 8바이트 할당하는군~” 을 알면되고,
“그럼 8바이트 밑(스택이니까 위로 쌓인다)에는 기존 ebp가 4바이트, 그보다 밑에는 함수 리턴 주소 4바이트가 있겠군”
“경계 검사가 없으니까 8바이트 + 4바이트로 ebp까지 밀어버리고, 리턴 주소를 덮어씌우면 되겠구만”
“쉘코드는 NOP 열심히 깔고 프로그램 인자로 대충 집어넣지 뭐”

의문점

  • 왜 복사하지? 굳이 strcpy로 복사하지 않아도 인자로 들어갈 때부터 이미 쉘코드가 삽입된 것 아닌가?
    • 맞다. 맞는데 틀렸다. 쉘 코드는 복사한 적이 없다. 다만 인자[1]을 버퍼로 복사했고, 그 과정에서 취약점에 의해 리턴 주소까지 덮어 씌워졌을 뿐이다.

힙 버퍼 오버플로우

힙이라고 예외가 아니다.
얘도 메모리에 연속적으로 할당되는 경우가 있다. (malloc은 기본적으로 여유를 두고 만들어지므로 예외)
notion image
이런 프로그램이 있다.
notion image
뭐 당연히 예상한대로 실행하면 func의 인자를 뱉는다.
그리고 메모리 구조를 분석해보면 buf 와 funcptr은 heap 영역에 연속적으로 할당되어있음을 알 수 있다.
notion image
notion image
notion image
그러면 strncpy가 경계검사가 소홀한 점(사실상 없다)을 활용해 얘도 funcptr이 가리키는 주소를 덮어씌울 수 있다.
buf에 64바이트 더미를 넣고 4바이트 주소를 추가로 넣으면 그렇게 될 것이다.
💡
이걸 방어하려면 어떻게 했어야 할까?
다른 보호 기법들을 적용하지 않았더라도 strlen() 대신 sizeof()로 고정된 값을 사용했어야 맞다.