Return to Shellcode란 ?
- Return address 영역에 Shellcode가 저장된 주소로 변경해, Shellcode를 호출하는 방식이다.
CALL & RET instruction
- Return to Shellcode를 이해하기 위해 CALL,RET 명령어에 대한 이해가 필요하다.
- CALL 명령어는 Return address(CALL 명령어 다음 명령어의 위치(주소 값))를 Stack에 저장하고, 피연산자 주소로 이동한다.
- RET 명령어는 POP 명령어를 이용해 RSP 레지스터가 가리키는 Stack영역에 저장된 값을 RIP(EIP)에 저장 후, 해당 주소로 이동한다.
- 즉, Stack영역에 저장된 Return address 값을 변경할 수 있다면 프로그램의 흐름을 변경 할 수 있다.
Instruction |
Processing |
Call <Operation> |
PUSH Return Address
JMP <Operation>
|
ret |
POP RIP
JMP RIP
|
Permissions in memory
- Return to Shellcode를 이해하기 위해 Memory 권한에 대한 이해가 필요하다.
- 프로그램에서 사용되는 메모리 영역을 모두 아래와 같이 권한들이 설정되어 있다.
- read(r) : 메모리 영역의 값을 읽을 수 있다.
- write(w) : 메모리 영역에 값을 저장 할 수 있다.
- excute(x) : 메모리 영역에서 코드를 실행 할 수 있다.
- GCC는 기본적으로 DEP가 적용되기 때문에 코드가 저장된 영역에만 실행권한이 설정되며, 데이터가 저장되는 영역에는 실행권한이 설정되지 않는다.
- 즉, Shellcode를 실행하기 위해 Shellcode가 저장된 영역은 excute 권한이 설정되어 있어야 한다.
Permissions in memory(DEP enabled)
Permissions in memory(DEP disabled)
- DEP를 해제하기 위해 GCC 옵션으로 "-z execstack" 를 추가해야 한다.
- 해당 옵션으로 인해 데이터 저장 영역에도 excute 권한이 설정되었다.
- 즉, Stack에 저장된 Shellcode를 실행 될 수 있다.
Proof of concept
- main() 함수는 vuln() 함수를 호출한다.
- vuln() 함수는 read() 함수를 이용해 사용자로 부터 100개의 문자를 입력받는다.
- 여기에서 취약성이 발생한다. buf 변수의 크기는 50byte이기 때문에 Stack Overflow가 발생한다.
- 0x5555555546d1 = vuln() 함수 종료 후 복귀할 주소.
- 0x55555555468a = vuln() 함수 시작 주소. 해당 위치에 bp를 걸고 continue.
- 다음과 같이 Return address를 확인 할 수 있다.
- 0x5555555546bb = read()함수 호출 주소에 bp 걸고 continue
- buf변수의 위치는 0x7fffffffe3a0이며, Return address 위치와 72byte 떨어져 있다.
- 즉, 사용자 입력 값으로 문자를 72개 이상 입력하면, Return address를 덮어쓸 수 있다.
- 다음과 같이 Return address 값이 변경된 것을 확인 할 수 있다.
- 0x7fffffffe3a0 영역에 0x4242424242424242(BBBBBBBB)가 저장되었다.
- 즉, 72개의 문자를 입력 후 shellcode가 저장된 주소를 저장하면 Return address를 덮어 쓸 수 있다.
Exploit
- x86_64 Shellcode
"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
Protection
- Memory 영역에서 코드가 실행되는 것을 차단하기 위해 NX Bit(DEP)를 적용 할 수 있다.
REF: https://www.lazenca.net/display/TEC/02.Return+to+Shellcode
댓글 영역