선행지식- 컴퓨터의 기본구조
프로그램 설치시 디스크에 저장되며
디스크에 저장되어있는 프로그램을 실행시켜 메모리로 불러온다.
이후에 메모리의 저장된 명령어들을 cpu가 실행시키며 프로그램이 최종적으로 실행하게 된다.
왼쪽 코드를 실행시켰다고 했을때 메모리가 저장되는 과정을 오른쪽에 나타낸 것 이다.
- 코드 영역 (.text):
- 프로그램의 실행 코드(명령어)가 저장됩니다.
- 보통 낮은 주소에 위치합니다.
- 데이터 영역 (Data):
- 초기화된 전역 변수들이 저장됩니다.
- 코드 영역 바로 위에 배치됩니다.
- BSS 영역:
- 초기화되지 않은 전역 변수들이 저장됩니다.
- 데이터 영역 위에 위치합니다.
- 힙 영역 (Heap):
- 동적 메모리 할당에 사용되는 영역입니다.
- 보통 낮은 주소에서 높은 주소로 성장합니다. (프로그램이 실행 중 필요할 때마다 메모리를 할당해나감)
- 스택 영역 (Stack):
- 함수 호출 시 생성되는 지역 변수와 함수 호출 정보를 저장합니다.
- 높은 주소에서 낮은 주소로 성장합니다. (즉, 스택은 함수가 호출될 때마다 위에서 아래로 쌓여 나감)
( int global = 2 같은 경우는 전역변수이다. 프로그램이 실행중인동안 해당 변수는 2로 지정이 되어있는 상태이며
int main() 안에 있는 int var = 3 은 main 함수가 실행되는동안만 변수가 지정되는 것 , 즉 지역변수 이므로
전역변수는 Data 영역에 , 지역변수는 Stack 영역에 들어가는 것이다.)
아까 말했듯 메모리에서 프로세스의 값과 결과를 cpu로 전달하는데 cpu도 기본적으로 이 값과 결과를 임시적으로 저장하여 빠르게 처리한다. cpu도 저장장치를 기본적으로 갖고있다는 것이다. (매우 용량이 작긴하지만)
이 cpu에 들어있는 기억장치를 레지스터라고 부른다.
어셈블리어는 컴퓨터가 이해할 수 있는 기계어(0과 1로 이루어진 코드)와 사람이 이해할 수 있는 고급 언어 사이의 다리 역할을 하는 언어이다.
예시로 우리가 파이썬 코드를 작성했다고 하자.
그럼 코드를 저장하면 디스크에 저장이되고 실행시 메모리가 디크스에서 꺼내와 cpu로 전달하게된다.
cpu는 한줄한줄 빠르게 cpu내에 저장장치 (레지스터)에 저장하고 빠르게 처리하며 실행하는데
cpu가 파이썬 코드를 이해할까? ㄴㄴ 못함
파이썬 인터프리터가 이를 기계어로 변환하여 CPU가 실행하게되는 것이다.
어셈블리어에서 함수 호출과 스택 프레임의 개념
가장먼저 대부분 프로시저 프롤로그가 이루어진다.
프로시저 프롤로그 ? - 이 일련의 명령어들은 함수의 스택 프레임을 설정하고 함수 실행에 필요한 환경을 준비하는 과정
RBP RSP 이런애들이 뭔지모르겠다면 참고
이렇게 보다보면 RBP , RSP 등등 뭔지모를 영어들이 나온다.
색깔별로 이해하면 쉽다. 핑크색은 스택부분을, 초록색은 인덱스 부분을, 파랑색은 연산이나 저장등 여러가지 용도에서 쓰이는 애들이다.
1. PUSH RBP -> RBP를 스택에 넣는다.
2. MOV RBP, RSP -> RSP를 RBP자리에 똑같이 복사하여 RBP, RSP 를 같은자리에 위치시킨다.
3. SUB RSP, 0x8 -> RSP를 8바이트 감소시켜 공간을 확보한다.
이게 프롤로그이다. 아마 앞으로 나올 대부분의 실습에서는 가장먼저 프롤로그가 열려 공간확보를 먼저 하게 될 것
4. 그런 다음, call 명령어로 func1 함수를 호출한다. 이 과정에서 func1에서 복귀할 주소가 스택에 저장된다.
- func1 함수가 호출되었고, PUSH RBP 명령어로 Main 함수의 RBP 값을 스택에 저장한다.
- 이후 func1은 자신의 스택 프레임을 설정하고 로컬 변수 공간을 할당한다.
한마디로 함수를 하나 불러오고 함수내에서도 똑같이 스택공간을 할당해주고 함수가 끝나면 돌아가는 중인것이다.
GDB
※ 실습 시스템을 활용한 리버스 엔지니어링 실습
먼저 실습대상 파일의 코드이다.
분석 파일을 대상으로 gdb (파일명) 입력해서 시작
disass main으로 메인함수 보이게 설정함 (오른쪽 사진)
프롤로그 부분
메인함수 실행 (func1(1,2,3,4,5,6,7) 을 실행중인것)
함수 에필로그
한줄한줄 실행시켜서 보면서 분석하게끔
원하는곳에 브레이크를 걸고 그전까진 전부 r 으로 실행시켜서 스킵하고
브레이크 건곳부터는 ni 명령어로 한줄한줄 실행될때마다 어떻게 바뀌는지 보는 중.
ni로 main+14 줄의 명령어가 실행됐을때 레지스터에 실제로 값이 들어간 것을 확인 할 수 있음.
시스템 취약점
• Memory Corruption : 개발자가 의도하지 않은 방식으로 동작하여 메모리 공간을 사용하거나 읽는 등 메모리를 오염시키는 행위(Crash를 통해 판단)
• Logical Bug : 프로그램의 논리적 오류로 인해 발생하게 되는 취약점(Crash가 발생하지 않으며 발견하기 어려움)
버퍼 오버플로우
buf에 16만큼 할당을 했을때 사용자의 입력길이 제한이 없다?
사용자가 16만큼 크기를 대충 채우고 이 크기이후에 악의적인 공격 페이로드를 입력하면
시스템에선 입력값을 받아내고 이후 동작을 해야하는데 (여기선 지정한 주소로 복귀할 예정이었음)
사용자의 악의적인 입력값을 수행하게됨
(위에 사진에선 7083으로 돌아가야할것을 사용자가 할당된크기 이후에 C를 넣음으로써 주소가 7083에서 0043으로 바뀌게 됨)
# bof_ex 파일 실행
exp@exp:~/exam/3. vuln/bof $ ./bof_ex
Input Name : aaaaa
My name is aaaaa
입력문구를 그대로 출력해주는 파일을 분석해보자.
16크기만큼 스택을 생성하고 주소로 돌아가고 있다.
# ‘A’ 16개 입력 Input Name : AAAAAAAAAAAAAAAA [엔터]
이렇게 스택에 A 16개를 다 채우는 원리.
할당된 크기 이상 입력시 A가 스택을 넘치게되는데 넘치는부분에는 돌아갈 주소로 되어있었다.
만약 넘치는부분에 A를 입력하는게 아닌 다른주소로 돌아가게끔 입력하면 악의적인 행동이 가능함.
2. Out-Of-Boundary
buf[10] 으로 1부터 10까지 숫자를 채워놓았고
buf값중에 3을 입력하면 buf[3] 안에 있는 숫자가 나오는 방식의 코드이다.
만약 사용자가 -1을 입력하면?? buf[-1]은 없는데 어떻게 될까?
에러가 나야하지만 이전에 할당된 temp라는 변수의 값을 출력하게 된다.
이런 구조로 되어있을것이고 buf[] 밑에있는 temp가 출력되서 temp변수에 있는 730이 출력되는것.
3. Off-by-one
예시로 1,2,3,4,5가 담긴 상자가 있다.
이 상자들을 복사해서 똑같이 상자에 담고 원래 상자들과 복사한 상자들의 주소를 출력해주는 프로그램이 있다.
스크린샷에 있는 프로그램은 원래 알파벳 배열과 복사된 알파벳 배열의 메모리 주소를 출력해주는 코드다. 여기서 Off-by-One 오류로 인해 복사된 배열의 마지막 요소가 원래 배열의 주소를 가리키는 문제가 발생하고 있다.
코드 실행 결과
프로그램은 두 배열의 각 요소에 대해 메모리 주소를 출력합니다. 원래 배열과 복사된 배열의 메모리 주소는 각기 다르지만, 복사된 배열의 마지막 요소가 원래 배열의 마지막 요소와 동일한 주소를 가지게 됩니다. 이는 복사 과정에서 Off-by-One 오류가 발생했음을 의미한다.
Off-by-One 오류 해결 방법
이런 오류를 방지하려면 다음과 같은 점을 주의해야 합니다:
- 정확한 반복문 조건 설정: 반복문을 사용할 때는 시작과 끝 조건을 정확하게 설정해야 합니다. 특히 배열 복사나 인덱스 계산 시 하나의 요소를 더하거나 덜 복사하는 일이 없도록 해야 합니다.
- 테스트와 검증: 배열이나 메모리와 관련된 작업을 할 때는 항상 경계값을 테스트하고, 예상치 못한 상황에서 프로그램이 어떻게 동작하는지 확인하는 것이 중요합니다.
4. Format String
포맷 스트링 실습
문제 : fsb_read 실행 시 file.txt 의 내용을 출력시키기.
1.먼저 특정 메모리 주소를 참조하거나 조작하기 위해 입력한 데이터가 몇번째 인자로 처리되는지 알아야한다.
쉽게 말하자면 우리가 공격을 하기위해 입력을받는부분에 악의적인 코드를 입력하게되면 해당 공격에 대한 결과물을 볼수있어야 한다. 따라서 입력받는 부분이 어디에 들어가는지 확인하기 위함.
AAAA %p %p ... 이렇게 %p를 늘려나가서 AAAA라는 데이터가 몇번째 %p (인자) 에 들어가는지 확인해본다.
2. 코드를 보면 file.txt 의 내용을 저장하는 flag_buf가 있다.
이 flag_buf의 주소값을 찾자.
gdb fsb32_read //gdb를 사용해 파일분석을 시작한다.
disass main //main 함수를 들어간다.
b *main+0 // 메인함수까지 브레이크 포인트를 설정한다.
r //브레이크 포인트까지 실행시킨다. (메인부터 볼꺼니까 그전에있던것들은 그냥 싹다 바로 실행시키는 과정)
ni // 한줄씩 실행시킨다. (계속 실행하면서 flag_buf의 주소를 찾아낸다.)
* 어떻게 찾나요?
사진처럼 코드를보면 찾아야할 flag_buf를 사용하는 부분까지 ni로 넘거가서 보고 주소를 얻어낸다.
3. 페이로드 작성. (리틀엔디안을 고려한 역순으로 작성하여 공격함)
파이썬으로 코드를 작성하지않을시 \x08 같은 부분을 하나의 문자로 해석하지않고 \x08을 하나하나 문자로 해석하기때문.
문제 해결
문제 2.
1번 문제랑 똑같이 메인함수까지 진입.
ChangeMe 변수를 사용하는 구간까지 건너뛰어서 해당 변수의 주소를 찾아냄.
해당 주소를 참조하는 포인트도 똑같이 몇번째 인자에 들어가는지 확인하기.
ChangeMe 변수에 327값 설정시 문제해결되는 부분을 보고
찾아낸 주소와 포인트 인자를 사용하여 페이로드 작성.
327만큼 문자를 채워주고 인자를 넣는 방식
똑같이 역순으로 페이로드 생성후 작성
메모리 보호 기법
ASLR( Address Space Layout Randomization) :
실행 파일과 관련된 공유 라이브러리, 스택, 힙이 매핑되는 메모리 영역의 주소를 랜덤으로 배치하는 것 = 프로그램 실행 시 메모리 주소를 랜덤하게 하는 보호기법
ASLR 이 왜 언제 나왔을까? : XP에서 비스타 , 7 으로 넘어갈때쯔음 나왔다.
XP시절에 PE file format이라는게 있었는데 여기에 Imagebase라고 하는게 있었다.
이거는 프로그램이 실행될때 메모리상에 올라갔을때 어디서부터 시작했을지
정해졌었다.
예를들어 exe파일은 0x400000
dll 파일은 0x1000000
이런식으로 지정이 되어있었다.
이게 지정이 되어있으면 왜안될까? : 거의다 같은 주소를 하게되었으니
메모리 주소가 고정이 되어있으면 공격할때의 패턴이 더 쉬워졌으며 보안적으로 좋지 못했다.
따라서 윈도우가 업그레이드 되며 ASLR 이 도입 되었다.
NX(No-Excute) bit : 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호기법
checksec ./nx ./non_nx
NX가 켜져있고 꺼져있는걸 확인할 수 있다.
gdb로 non_nx에 들어가 메인에 브레이크걸고 가서 vmmap 입력
bof가 발생할때 스택의 공간을 넘어가서 그 이외의 영역에 코드가 덮어씌워지는 문제가 있었는데 스택에 미리 지정된 값(카나리아)을 넣고, 함수가 끝날 때 그 값이 변하지 않았는지 확인합니다.
'K쉴드 주니어 수업 정리' 카테고리의 다른 글
유닉스 보안 진단 가이드: 항목별 설정 체크 (1) | 2024.09.16 |
---|---|
프로젝트 주제 선정과정 (0) | 2024.09.01 |
5일차 (0) | 2024.08.26 |
4일차 (0) | 2024.08.21 |
1일차 (0) | 2024.08.12 |