DINGA DINGA
article thumbnail
728x90

Return Address Overwrite

1. RET Overwrite - 1

- 리턴 주소를 덮어 실행 흐름을 조작해 공격자가 원하는 코드를 실행함.

 

2. RET Overwrite - 2

example1.c

- example1.c에서는 argv[1]을 vuln함수의 인자로 전달함.

- vuln함수에서는 src 버퍼를 buf 버퍼에 strcpy함수를 이용해 복사함.

- strcpy 함수는 피복사 버퍼에 대한 길이 검증이 없기 때문에, 프로그램의 첫번째 인자에 buf 배열의 크기보다 긴 문자열을 넣으면 스택 버퍼 오버플로우가 발생함.

vuln 함수의 메모리 구조

- x86 아키텍처 호출 규약에 의해 vuln함수가 호출되면 vuln함수의 인자인 src 문자열 포인터가 스택에 먼저 쌓임 → vuln함수의 리턴 주소가 쌓임 → 함수의 프롤로그에서 ebp 레지스터를 저장한 다음 지역 변수의 공간을 할당

 

3. RET Overwrite - 3

- 취약점이 존재하는 vuln함수에 브레이크포인트를 설정한 후 첫번째 인자와 함께 example1의 방너리 실행

- 브레이크포인트에서의 스택 메모리를 보면 첫 4바이트느 vuln함수의 리턴 주소이고 다음 4바이트는 vuln함수의 인자인 argv[1]의 주소

 

4. RET Overwrite - 4

- strcpy 함수가 실행되기 직전에 브레이크포인트를 설정

- 첫 번째 인자인 buf 주소와 두 번째 인자인 argv[1]의 주소가 저장되어 있는 것을 볼 수 있음.

 

- strcpy 함수를 실행하자 첫 번째 인자인 buf(0xffffd4fc)에 argv[1]의 문자열이 복사된 것을 볼 수 있음.

- argv[1]에 buf의 길이인 32바이트보다 긴 문자열을 주었기 때문에 vuln의 리턴 주소가 저장된 0xffffd520 너머까지 argv[1] 문자열이 복사됨.

 

5. RET Overwrite - 5

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

- vuln 함수가 리턴할 때의 esp 레지스터가 가리키고 있는 주소에는 0x6a6a6a6a가 저장되어 있음.

- 이후 ret 명령어가 실행되면 eip 레지스터는 0x6a6a6a6a가 됨.

스택 버퍼 오버플로우 취약점을 통해 프로그램 실행 흐름이 조작된 모습

- eip 레지스터를 임의의 값으로 바꿀 수 있으므로 원하는 주소의 코드 실행 가능

- Linux Exploitation은 로컬 환경의 타겟을 대상으로 하기 때문에 익스플로잇 최종 목표는 프로그램의 실행 흐름을 조작하여 /bin/sh 혹은 셸 바이너리를 실행하는 것

- 셸: 커맨드 라인의 명령어 혹은 스크립트를 받아 서버에서 그에 맞는 기능을 실행시켜주는 프로그램

- 취약점이 존재하는 바이너리를 익스플로잇하여 셸 프로그램을 실행하면 해당 바이너리 권한의 셸을 획득하여 서버에 임의의 명령어를 실행할 수 있음.

 

6. RET Overwrite - 6

- 공격자가 /bin/sh 혹은 셸 바이너리를 실행하는 기계어 코드를 실행한다면, 셸에서 제공하는 여러 명령어를 실행할 수 있음.

- 리눅스에서는 바이너리를 실행시키기 위해 execve 시스템 콜을 사용함.

execve 시스템 골의 인자

- pathname: 실행시킬 바이너리의 경로

- argv: 프로그램의 인자 포인터 배열

- envp: 프로그램의 환경변수 포인터 배열

- sys_execve("/bin/sh" 주소, NULL, NULL) 형태로 호출하는 기계어 코드를 만드는 과정

: sys_execve("/bin/sh" 주소, NULL, NULL) 를 실행하는 어셈블리 코드를 만듦 → 만들어진 어셈블리 코드를 기계어 코드(셸코드)로 바꿈

어셈블리 코드 - shellcode.asm
어셈블리 코드를 기계어로 변환 - shellcode.o
기계어 코드 - shellcode.bin

 

7. RET Overwrite - 7

- 만들어진 셸코드를 example1 프로그램의 인자로 전달하면 셸코드가 스택 메모리에 저장됨.

- 위의 디버깅 결과를 보면 eip 레지스터 값이 0x6a6a6a6a로 바뀌었고, 이는 argv[1]의 36번째 오프셋.

- 리턴 주소가 저장된 스택 메모리를 덮기까지 남은 36바이트의 위치에 셸코드를 위치시키고, 리턴 주소를 저장된 셸코드 주소로 바꾸는 공격 코드 작성.

 

8. RET Overwrite - 8

- 셸코드 주소를 확인하기 위해 argv[1]에 40바이트의 문자열을 넣어 example1 바이너리를 실행하고, strcpy 함수를 호출하는 주소에 브레이크포인트를 설정해 디버깅함.

- strcpy함수의 첫 번째 인자는 buf(0xffffd4fc)이므로 strcpy함수가 실행된 이후 셸코드는 0xffffd4fc에 저장됨.

- 셸코드의 길이는 23바이트이므로 공격 코드는 다음과 같이 구성

"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80" + "\x90”*13 + “\xfc\xd4\xff\xff”

셸코드 + 임의의 13바이트 + 0xffffd4fc

공격코드를 argv[1]에 넣어 프로그램을 실행한 모습

- 리턴 주소가 셸코드 주소로 바뀌어 셸이 실행되는 것을 확인할 수 있음.

 

9. RET Overwrite - 9

공격코드를 gdb가 아닌 셸 환경에서 실행한 모습

- 셸을 획득하지 못하고 프로그램이 비정상적으로 종료됨.

- 스택의 셸코드 주소가 바뀌었기 때문.

- 프로그램을 다른 환경에서 실행할 때 지역변수의 주소는 스택 끝에 존재하는 프로그램의 인자와 환경변수에 따라 변화함.

- 예시의 gdb와 셸에서의 지역변수 주소가 다른 이유는 argv[0] 문자열, 즉 실행 파일 경로가 각각 절대 경로와 상대 경로로 다르기 때문.

- gdb는 프로그램을 실행할 때 실행 파일의 절대 경로를 argv[0]에 저장하지만, 셸에서 프로그램을 실행할 때는 사용자가 입력한 경로가 argv[0]에 저장됨. 따라서 스택 주소에 약간의 오차가 생겨도 익스플로잇이 성공할 수 있도록 공격 코드를 수정해야 함.

 

NOP Sled

1. NOP Sled - 1

- NOP: "No OPeration"의 약자로, 명령어 중의 하나.

- 프로그램의 실행에 영향을 주지 않는 명령어이기 때문에 프로그램이 실행 중에 NOP 명령어를 만나면 다음 명령어로 넘어가는 것과 같은 효과를 줌.

- 주로 명령어의 주소 alignment를 맞출 때 사용됨.

- x86 아키텍처의 NOP 명령어 바이트코드: 0x90

- NOP Sled (NOP Slide): 주로 셸코드의 주소를 정확히 알아내기 힘들 경우 큰 메모리를 확보하여 셸코드 주소의 오차 범위를 크게 만들 때 사용.

- 예를 들어, 0x100 주소에 셸코드가 저장되어 있을 때, NOP Sled가 없으면 정확히 0x100 주소로 실행 흐름을 바꿔야 셸코드가 실행되지만 셸코드 앞에 0x10000바이트의 NOP Sled를 붙이면 0x100 ~ 0x10100 중 임의의 주소로 실행 흐름을 바꾸기만 하면 됨.

- NOP Sled의 어딘가의 주소를 알아내 달라진 환경에서 해당 주소로 실행 흐름을 바꾸는 경우 평균적으로 0x10000/2만큼의 주소 오차가 허용.

 

2. NOP Sled - 2

- vuln 함수에서 strcpy함수를 호출하는 시점에 브레이크포인트를 설정해 복사 버퍼인 argv[1]의 주소를 알아냄.

NOP Sled의 중간 지점의 주소

- 이 주소를 이용한 새 공격 코드는 아래와 같이 구성할 수 있음.

"A" * 36 + 0xffff13f9 + "\x90" * 100000 + shellcode

성공적으로 셸이 실행된 모습

728x90

'SYSTEM > 개념 정리' 카테고리의 다른 글

[Dream hack] Memory Corruption - C (I) 中 스택 버퍼 오버플로우  (0) 2021.03.04
BOF 정리 (2)  (0) 2021.02.24
BOF 정리 (1)  (0) 2021.02.21
리눅스 기초 명령어(2)  (0) 2021.02.19
리눅스 기초 명령어  (0) 2021.02.15