dreamhack: Linux Exploitation & Mitigation Part 1

133210·2021년 7월 21일
0

2020 시스템

목록 보기
5/25
post-thumbnail

1.1 ELF 동적 분석

리눅스 실행 파일인 ELF 파일을 동적으로 디버깅하는 방법

1) 도구: gdb, strace, Itrace, IDA
2) gdbinit: gdb를 시작할 때 자동적으로 실행할 gdb 명령어들을 저장하고 있는 파일
3) b: break point 설정
4) info: 디버깅 중인 프로세스의 정보 출력.
5) run(r): 프로세스를 실행시켜 주는 명령어
6) eip 레지스터를 통해 프로그램이 어디까지 실행되었는지 확인 가능
7) print(p): 레지스터나 변수의 값을 출력시켜줌
8) continue(c): 프로세스가 멈춰있는 상태에서 프로세스를 이어서 실행시켜 줌
9) x: 인자로 주어진 주소의 메모리를 볼 수 있음. (출력 타입 지정 가능)
10) nexti(ni): 함수의 다음 인스트럭션까지 실행해 줌

1.2 Process Attach

1) -p PID or –pid=PID: gdb에 -p PID 혹은 –pid PID를 인자로 전달하면
PID에 해당하는 프로세스에 gdb를 attach할 수 있음
2) 실행중인 프로세스의 PID 알아보기
- ps 프로그램 이용하기
- pidof나 pgrep 프로그램 사용하기 (pidof나 pgrep의 인자로 바이너리 이름을 전달하면 바이너리의 PID를 구할 수 있음)
3) gdb를 프로세스에 attach 시킴으로 프로세스 디버깅 가능

Info functions: 함수의 이름과 주소를 출력
Disassemble main: Main 함수의 디스어셈블리를 출력
Break: 주소에 breakpoint를 설정
Info breakpoints: Breakpoint 정보 출력
Run: 프로그램을 처음부터 실행
Display: 매 실행 시 인자로 전달된 값을 출력
Continue: 다음 브레이크포인트까지 실행
Si: Step instruction, 명령어 1개 실행
Finish: 현재 함수를 모두 실행
Info register: 레지스터 정보 출력
x/: 지정된 메모리 영역을 특정 단위로 표현
P: Print, 인자로 전달된 값을 출력
Delete [break number]: 브레이크포인터 번호에 해당하는 브레이크포인트 삭제
Quit: Gdb 종료

2.1 Return Address Overwrite

스택 버퍼 오버플로우 취약점을 공격하는 방법

return address: 함수가 끝나고 돌아갈 이전 함수의 주소
스택에 저장된 리턴 주소를 다른 값으로 바꾸면 실행 흐름을 조작할 수 있음

example1에서 vuln 함수의 메모리 구조

vuln 함수의 인자인 src 문자열 포인터가 스택에 먼저 쌓이고, 이후 vuln 함수의 리턴 주소,
함수의 프롤로그에서 ebp 레지스터를 저장한 후 지역변수의 공간을 할당함

앞에 빈공간은 buf[32]+sfp[4] = 36칸
36칸에 데이터를 채워넣고 ret인 나머지 4칸은 0x6a인 j로 채워넣음

2.2 RET Overwrite

ret 명령어: esp 레지스터가 가리키고 있는 주소에 저장된 값으로 점프하는 명령어

2.3 셸코드

1) 공격자가 /bin/sh 혹은 셸 바이너리를 실행하는 기계어 코드를 실행한다면, 셸에서 제공하는 여러 명령어 실행 가능
2) execve 시스템 콜: 리눅스에서 바이너리를 실행하기 위해 사용되는 시스템 콜

pathname에는 실행시킬 바이너리의 경로, argv는 프로그램의 인자 포인터 배열,
envp에는 프로그램의 환경변수 포인터 배열이 요구됨.
최종적으로는 다음 인자 형태의 execve 시스템 콜을 호출하면 됨
sys_execvc(“/bin/sh” 주소, NULL, NULL)
3) 셸코드: sys_execve(“/bin/sh” 주소, NULL, NULL)을 실행하는 어셈블리 코드 만들기
-> 만들어진 어셈블리 코드를 기계어 코드로 바꾸기

2.3 RET Overwrite Exploitation

1) 만들어진 셸코드를 example1 프로그램의 인자로 전달하면 셸코드가 스택 메모리에 저장됨
2) vuln 함수의 리턴 주소를 스택에 저장된 셸코드의 주소로 바꾸기

  • 셸코드의 주소를 확인하기 위해 argvp[1]에 40바이트 길이의 문자열을 넣어 example1 바이너리 실행, strcpy 함수 호출 자리에 브레이크 포인트를 설정함.
  • 셸코드의 길이는 23바이트이므로 공격코드는 ‘셸코드+임의의 13바이트+주소값’으로 구성
  • 공격코드를 argv[1]에 넣어 프로그램을 실행하면 리턴주소셸코드 주소로 바뀌어 셸이 실행되는 것을 볼 수 있음 (gdb에서 실행)
  • gdb가 아닌 셸 환경에서 실행할 시 스택의 셸코드 주소가 바뀌어 프로그램이 비정상 종료됨
  • 본 예시에서 지역 변수 주소가 다른 이유는 argv[0] 문자열, 즉 실행 파일의 경로가 각각 절대 경로상대 경로로 다르기 때문
  • gdb는 프로그램을 실행할 때 실행 파일의 절대 경로를 argv[0]에 저장하지만, 셸에서 프로그램을 실행할 때는 사용자가 입력한 경로가 argv[0]에 저장됨

3.1 NOP Sled

1) NOP: No OPeration의 약자, xchg eax, eax와 같이 프로그램의 실행에 영향을 주지 않는 명령어이기 때문에 프로그램이 실행 중에 NOP 명령어를 만나면 다음 명령어로 넘어가는 것과 같은 효과를 줌. 주로 명령어의 주소 alignment를 맞출 때 사용됨
2) x86 아키텍처의 NOP 명령어 바이트코드는 0x90
3) NOP Sled: 주로 셸코드의 주소를 정확히 알아내기 힘들 경우 큰 메모리를 확보하여 셸코드 주소의 오차 범위를 크게 만들 때 사용

3.2 Exploitation Using NOP Sled

1) NOP Sled를 이용하여 새로운 공격코드 만들기

vuln 함수 disassemble 하고 strcpy 함수를 call하는 주소인 0x08048467에 breakpoint를 설정함

복사 버퍼인 argv[1]의 주소인 0xfffe4c3f를 알아냄
NOP Sled 중간 지점의 주소인 argv1+5000은 0xffff0f8f
새로운 공격 코드는 “A”36+0xffff0f8f+”\x90”100000+쉘코드로 이뤄짐

4.1 NX bit의 등장

1) No-eXecute bit(NX bit): 프로그램의 공격을 어렵게 하기 위해, 메모리에 쓰기 권한과 실행 권한을 동시에 부여하지 않음
2) 서로 다른 컴파일 옵션을 통해 NX bit 보호 기법을 적용한 example2_nx 바이너라와 적용하지 않은 example_x 바이너리 생성
3) example_x 바이너리를 실행하면 정상적으로 셸코드가 실행되어 셸이 실행됨. 또한 권한을 확인하면 스택과 데이터 영역 모두 rwx(읽기, 쓰기, 실행 권한)을 가지고 있음
4) example_nx 바이너리를 실행하면 데이터 영역에 실행 권한이 없기 때문에 Sementation Fault 발생. 권한을 확인하면 스택과 데이터 영역 메모리 모두 rw(읽기, 쓰기 권한)만을 가지고 있음

4.2 Bypassing NX Bit

1) NX bit가 설정되어 있을 경우에 쓰기 권한과 실행 권한이 동시에 있는 메모리 영역은 존재하지 않음.
2) 셸코드를 스택 메모리에 저장해 실행 흐름을 스택으로 바꾸는 공격은 사용할 수 없으나, 프로그램에 스택 버퍼 오버플로우가 존재한다면 실행 흐름을 임의의 주소로 바꾸는 것은 여전히 가능함.
3) 메모리의 실행 가능한 영역에 있는 코드들을 활용해서 익스플로잇 해야 함
4) C언어에서 printf와 같은 라이브러리 함수가 사용될 때, 호출된 함수의 주소를 찾아 실행함. 그러므로 프로그램에서 호출된 함수 이외에 system과 같이 익스플로잇에 유용한 함수 코드들도 함께 로딩됨.
5) 리턴 주소를 이와 같은 방법으로 알아낸 라이브러리 함수의 주소로 바꾸면 해당하는 함수를 호출할 수 있음

4.3 RTL

1) Return To Libc(RTL): 리턴 주소를 라이브러리 내에 존재하는 함수의 주소로 바꿔 NX bit를 우회하는 공격 기법
2) libc.so.6 라이브러리에는 execve, execlp, execl, execvp, system, popen 등 프로그램을 실행할 수 있는 다양한 함수들이 존재
3) 이 중 system 함수는 인자를 하나만 받기 때문에 익스플로잇 시 많이 사용됨.
4) system 함수의 인자는 실행할 셸 명령어 문자열의 주소이기 때문에, “/bin/sh” 문자열의 주소를 system 함수의 인자로 넘겨준 후 호출하면 /bin/sh 바이너리가 실행됨

4.4 NX bit가 설정되어 있는지 확인하는 방법

1) 스택 메모리의 권한을 검사하는 것으로도 충분함
2) ELF 바이너리 분석 도구인 readelf를 사용할수도 있음
(readelf -a (분석파일) | grep STACK)
3) 실행되고 있는 바이너리의 메모리 맵에 있는 권한 확인하기

0개의 댓글